Repository: Quandela/Perceval Branch: main Commit: 0f2bcdb4984c Files: 418 Total size: 10.9 MB Directory structure: gitextract_lva1xgfi/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── rendering_requirements.txt │ └── workflows/ │ ├── autotests.yml │ ├── benchmarks.yml │ ├── build-and-deploy-docs.yml │ ├── python-publish.yml │ ├── rerun-notebooks-and-build-docs.yml │ └── rerun_notebooks.sh ├── .gitignore ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── benchmark/ │ ├── benchmark-circuit-building.py │ ├── benchmark-unitary.py │ ├── benchmark_QML-DE-solver.py │ ├── benchmark_bosonsampling.py │ ├── benchmark_pdisplay.py │ └── benchmark_stepper.py ├── conftest.py ├── docs/ │ ├── Makefile │ ├── make.bat │ ├── multiversion_config/ │ │ └── conf.py │ ├── requirements.txt │ └── source/ │ ├── _static/ │ │ └── css/ │ │ └── style.css │ ├── _templates/ │ │ ├── layout.html │ │ ├── page.html │ │ └── versions.html │ ├── backends.rst │ ├── bibliography.rst │ ├── build_catalog.py │ ├── conf.py │ ├── contributing.rst │ ├── examples_boson_sampling.rst │ ├── examples_others.rst │ ├── examples_quantum_walk.rst │ ├── examples_standard_algo.rst │ ├── examples_vqa.rst │ ├── getting_started.rst │ ├── index.rst │ ├── legacy.rst │ ├── notebooks/ │ │ ├── 2-mode_Grover_algorithm.ipynb │ │ ├── Advanced_state_tutorial.ipynb │ │ ├── BS-based_implementation.ipynb │ │ ├── Boson_Bunching.ipynb │ │ ├── Boson_sampling.ipynb │ │ ├── Circuit_Tutorial.ipynb │ │ ├── Computation_Tutorial.ipynb │ │ ├── Density_matrix_Fock_space.ipynb │ │ ├── Differential_equation_resolution.ipynb │ │ ├── Encoding_Tutorial.ipynb │ │ ├── Gedik_qudit.ipynb │ │ ├── Graph_States_Tutorial.ipynb │ │ ├── LOv_rewriting_rules.ipynb │ │ ├── MPS_techniques_for_boson_sampling.ipynb │ │ ├── QLOQ_QUBO_tutorial.ipynb │ │ ├── QUBO.ipynb │ │ ├── Quantum_teleportation_feed_forward.ipynb │ │ ├── Reinforcement_learning.ipynb │ │ ├── Remote_Computation_Tutorial.ipynb │ │ ├── Shor_Implementation.ipynb │ │ ├── Simulation_non-unitary_components.ipynb │ │ ├── State_Tutorial.ipynb │ │ ├── Tomography_walkthrough.ipynb │ │ ├── Two-particle_bosonic-fermionic_quantum_walk.ipynb │ │ ├── VQA_Tutorial.ipynb │ │ ├── Variational_Quantum_Eigensolver.ipynb │ │ ├── Walkthrough-cnot.ipynb │ │ ├── quantum_kernel_methods.ipynb │ │ └── requirements.txt │ ├── reference/ │ │ ├── algorithm/ │ │ │ ├── analyzer.rst │ │ │ ├── index.rst │ │ │ ├── sampler.rst │ │ │ └── tomography.rst │ │ ├── backends/ │ │ │ ├── clifford2017.rst │ │ │ ├── index.rst │ │ │ ├── mps.rst │ │ │ ├── naive.rst │ │ │ ├── naive_approx.rst │ │ │ ├── slap.rst │ │ │ └── slos.rst │ │ ├── components/ │ │ │ ├── catalog.rst │ │ │ ├── circuit.rst │ │ │ ├── detector.rst │ │ │ ├── experiment.rst │ │ │ ├── feed_forward_configurator.rst │ │ │ ├── generic_interferometer.rst │ │ │ ├── index.rst │ │ │ ├── non_unitary_components.rst │ │ │ ├── port.rst │ │ │ ├── processor.rst │ │ │ ├── source.rst │ │ │ └── unitary_components.rst │ │ ├── error_mitigation.rst │ │ ├── exqalibur/ │ │ │ ├── circuit_optimizer.rst │ │ │ ├── clifford2017.rst │ │ │ ├── config.rst │ │ │ ├── fockstate.rst │ │ │ ├── fs_utils.rst │ │ │ ├── index.rst │ │ │ ├── permanent.rst │ │ │ ├── postselect.rst │ │ │ ├── slap.rst │ │ │ ├── slos.rst │ │ │ ├── source.rst │ │ │ ├── state_data_structure.rst │ │ │ └── statevector.rst │ │ ├── providers.rst │ │ ├── rendering/ │ │ │ ├── display_config.rst │ │ │ ├── index.rst │ │ │ ├── pdisplay.rst │ │ │ └── skins.rst │ │ ├── runtime/ │ │ │ ├── index.rst │ │ │ ├── job.rst │ │ │ ├── job_group.rst │ │ │ ├── job_status.rst │ │ │ ├── remote_config.rst │ │ │ ├── remote_processor.rst │ │ │ └── rpchandler.rst │ │ ├── serialization.rst │ │ ├── simulators/ │ │ │ ├── ff_simulator.rst │ │ │ ├── index.rst │ │ │ ├── noisy_sampling_simulator.rst │ │ │ ├── simulator.rst │ │ │ ├── simulator_factory.rst │ │ │ └── stepper.rst │ │ ├── utils/ │ │ │ ├── conversion.rst │ │ │ ├── density_matrix.rst │ │ │ ├── dist_metrics.rst │ │ │ ├── distinct_permutations.rst │ │ │ ├── expression.rst │ │ │ ├── index.rst │ │ │ ├── logging.rst │ │ │ ├── logical_state.rst │ │ │ ├── matrix.rst │ │ │ ├── noise_model.rst │ │ │ ├── parameter.rst │ │ │ ├── persistent_data.rst │ │ │ ├── polarization.rst │ │ │ ├── random_seed.rst │ │ │ ├── stategenerator.rst │ │ │ └── states.rst │ │ └── utils_algorithms/ │ │ ├── circuit_optimizer.rst │ │ ├── index.rst │ │ └── simplify.rst │ ├── references.bib │ ├── tutorial_advanced.rst │ ├── tutorial_beginner.rst │ └── tutorial_expert.rst ├── perceval/ │ ├── __init__.py │ ├── algorithm/ │ │ ├── __init__.py │ │ ├── abstract_algorithm.py │ │ ├── analyzer.py │ │ ├── sampler.py │ │ └── tomography/ │ │ ├── __init__.py │ │ ├── abstract_process_tomography.py │ │ ├── tomography.py │ │ ├── tomography_mle.py │ │ └── tomography_utils.py │ ├── backends/ │ │ ├── __init__.py │ │ ├── _abstract_backends.py │ │ ├── _clifford2017.py │ │ ├── _mps.py │ │ ├── _naive.py │ │ ├── _naive_approx.py │ │ ├── _slap.py │ │ └── _slos.py │ ├── components/ │ │ ├── __init__.py │ │ ├── _decompose_perms.py │ │ ├── _mode_connector.py │ │ ├── _pauli.py │ │ ├── abstract_component.py │ │ ├── abstract_processor.py │ │ ├── compiled_circuit.py │ │ ├── component_catalog.py │ │ ├── core_catalog/ │ │ │ ├── __init__.py │ │ │ ├── _helpers/ │ │ │ │ ├── __init__.py │ │ │ │ ├── entanglement_qloq.py │ │ │ │ └── rotations_qloq.py │ │ │ ├── controlled_rotation_gates.py │ │ │ ├── gates_1qubit.py │ │ │ ├── generic_2mode.py │ │ │ ├── heralded_cnot.py │ │ │ ├── heralded_cz.py │ │ │ ├── klm_cnot.py │ │ │ ├── mzi.py │ │ │ ├── postprocessed_ccz.py │ │ │ ├── postprocessed_cnot.py │ │ │ ├── postprocessed_cz.py │ │ │ ├── qloq_ansatz.py │ │ │ └── toffoli.py │ │ ├── detector.py │ │ ├── experiment.py │ │ ├── feed_forward_configurator.py │ │ ├── generic_interferometer.py │ │ ├── linear_circuit.py │ │ ├── non_unitary_components.py │ │ ├── port.py │ │ ├── processor.py │ │ ├── source.py │ │ ├── tomography_exp_configurer.py │ │ └── unitary_components.py │ ├── error_mitigation/ │ │ ├── __init__.py │ │ ├── _loss_mitigation_utils.py │ │ └── loss_mitigation.py │ ├── providers/ │ │ ├── __init__.py │ │ ├── quandela/ │ │ │ ├── __init__.py │ │ │ └── quandela_session.py │ │ └── scaleway/ │ │ ├── __init__.py │ │ ├── scaleway_rpc_handler.py │ │ └── scaleway_session.py │ ├── rendering/ │ │ ├── __init__.py │ │ ├── _density_matrix_utils.py │ │ ├── _processor_utils.py │ │ ├── canvas/ │ │ │ ├── __init__.py │ │ │ ├── canvas.py │ │ │ ├── latex_canvas.py │ │ │ ├── mplot_canvas.py │ │ │ └── svg_canvas.py │ │ ├── circuit/ │ │ │ ├── __init__.py │ │ │ ├── _canvas_shapes.py │ │ │ ├── abstract_skin.py │ │ │ ├── canvas_renderer.py │ │ │ ├── create_renderer.py │ │ │ ├── debug_skin.py │ │ │ ├── display_config.py │ │ │ ├── phys_skin.py │ │ │ ├── renderer_interface.py │ │ │ ├── skin_common.py │ │ │ ├── symb_skin.py │ │ │ └── text_renderer.py │ │ ├── drawsvg_wrapper.py │ │ ├── format.py │ │ ├── mplotlib_renderers/ │ │ │ ├── __init__.py │ │ │ ├── _mplot_utils.py │ │ │ ├── density_matrix_renderer.py │ │ │ ├── graph_renderer.py │ │ │ └── tomography_renderer.py │ │ └── pdisplay.py │ ├── runtime/ │ │ ├── __init__.py │ │ ├── check_cancel.py │ │ ├── job.py │ │ ├── job_group.py │ │ ├── job_status.py │ │ ├── local_job.py │ │ ├── payload_generator.py │ │ ├── remote_config.py │ │ ├── remote_job.py │ │ ├── remote_processor.py │ │ ├── rpc_handler.py │ │ └── session.py │ ├── serialization/ │ │ ├── __init__.py │ │ ├── _circuit_serialization.py │ │ ├── _component_deserialization.py │ │ ├── _constants.py │ │ ├── _detector_serialization.py │ │ ├── _experiment_serialization.py │ │ ├── _matrix_serialization.py │ │ ├── _parameter_serialization.py │ │ ├── _port_deserialization.py │ │ ├── _schema_circuit_pb2.py │ │ ├── _serialized_containers.py │ │ ├── _state_serialization.py │ │ ├── deserialize.py │ │ ├── perceval-serialization │ │ ├── serialize.py │ │ └── serialize_binary.py │ ├── simulators/ │ │ ├── __init__.py │ │ ├── _simulate_detectors.py │ │ ├── _simulator_utils.py │ │ ├── delay_simulator.py │ │ ├── feed_forward_simulator.py │ │ ├── loss_simulator.py │ │ ├── noisy_sampling_simulator.py │ │ ├── polarization_simulator.py │ │ ├── simulator.py │ │ ├── simulator_factory.py │ │ ├── simulator_interface.py │ │ └── stepper.py │ └── utils/ │ ├── __init__.py │ ├── _enums.py │ ├── _random.py │ ├── _validated_params.py │ ├── algorithms/ │ │ ├── __init__.py │ │ ├── circuit_optimizer.py │ │ ├── decomposition.py │ │ ├── match.py │ │ ├── norm.py │ │ ├── optimize.py │ │ ├── simplification.py │ │ └── solve.py │ ├── conversion.py │ ├── density_matrix.py │ ├── density_matrix_utils.py │ ├── dist_metrics.py │ ├── format.py │ ├── globals.py │ ├── logging/ │ │ ├── __init__.py │ │ ├── config.py │ │ └── loggers.py │ ├── logical_state.py │ ├── matrix.py │ ├── mlstr.py │ ├── noise_model.py │ ├── parameter.py │ ├── persistent_data.py │ ├── polarization.py │ ├── postselect.py │ ├── progress_cb.py │ ├── qmath.py │ ├── stategenerator.py │ ├── states.py │ └── versions/ │ ├── __init__.py │ ├── metadata.py │ └── version_utils.py ├── pyproject.toml ├── pytest.ini ├── requirements.txt ├── setup.py └── tests/ ├── __init__.py ├── _test_utils.py ├── algorithm/ │ ├── __init__.py │ ├── test_analyzer.py │ ├── test_sampler.py │ ├── test_tomography.py │ └── test_tomography_mle.py ├── backends/ │ ├── __init__.py │ └── test_backends.py ├── components/ │ ├── __init__.py │ ├── catalog/ │ │ ├── __init__.py │ │ ├── test_1_qubit_gates.py │ │ ├── test_2qbits_gates.py │ │ ├── test_pauli.py │ │ └── test_qloq.py │ ├── test_circuit.py │ ├── test_compiled_circuit.py │ ├── test_compute_unitary.py │ ├── test_controlled_gates.py │ ├── test_decomposition.py │ ├── test_detectors.py │ ├── test_ff_configurator.py │ ├── test_generic_interferometer.py │ ├── test_loss_channel.py │ ├── test_match.py │ ├── test_mode_connector.py │ ├── test_permutation.py │ ├── test_port.py │ ├── test_processor.py │ ├── test_processor_composition.py │ ├── test_source.py │ ├── test_time_delay.py │ ├── test_transfer.py │ └── test_unitary_determinant.py ├── data/ │ ├── u_hom │ ├── u_hom_sym │ ├── u_random_2 │ ├── u_random_3 │ └── u_random_8 ├── error_mitigation/ │ ├── __init__.py │ └── test_loss_mitigation.py ├── rendering/ │ ├── __init__.py │ ├── test_rendering.py │ ├── test_visualization.py │ └── test_visualization_ide.py ├── requirements.txt ├── runtime/ │ ├── __init__.py │ ├── _mock_rpc_handler.py │ ├── test_job.py │ ├── test_job_group.py │ ├── test_payload_generation.py │ ├── test_remote_config.py │ ├── test_remote_job.py │ ├── test_rpc_handler.py │ └── test_shots_estimate.py ├── serialization/ │ ├── __init__.py │ ├── test_serialization.py │ └── test_serialized_containers.py ├── simulators/ │ ├── __init__.py │ ├── test_delay_simulator.py │ ├── test_ff_simulator.py │ ├── test_loss_simulator.py │ ├── test_noisy_sampling_simulator.py │ ├── test_polarization_simulator.py │ ├── test_samples_provider.py │ ├── test_simulate_detectors.py │ ├── test_simulator.py │ ├── test_simulator_factory.py │ ├── test_simulator_utils.py │ └── test_stepper.py ├── test_test_utils.py └── utils/ ├── __init__.py ├── test_circuit_optimizer.py ├── test_density_matrix.py ├── test_dist_metrics.py ├── test_doc_config.py ├── test_fidelity.py ├── test_format.py ├── test_log.py ├── test_logical_state.py ├── test_mask.py ├── test_matrix.py ├── test_metadata.py ├── test_mlstr.py ├── test_noise_model.py ├── test_optimize.py ├── test_parameter.py ├── test_persistent_data.py ├── test_polarization.py ├── test_postselect.py ├── test_qmath.py ├── test_seed.py ├── test_simplification.py ├── test_stategenerator.py ├── test_statevector.py ├── test_tensorproduct.py └── test_utils_conversion.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Prerequisite** Please ensure that this bug has not been answered on the [Perceval forum](https://perceval.quandela.net/forum) **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. macOS, Ubuntu] - CPU Arch: [e.g. x86_64, Apple M1, ARMv7] - Python Version: [e.g. 3.13] - Perceval Version: [e.g. 0.3.3] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Prerequisite** Please ensure this has not been requested on the [Perceval forum](https://perceval.quandela.net/forum) **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/rendering_requirements.txt ================================================ contourpy cycler drawsvg fonttools kiwisolver latexcodec matplotlib pillow pyparsing python-dateutil six ================================================ FILE: .github/workflows/autotests.yml ================================================ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Auto Tests on: push: branches: - main - release/* pull_request: branches: - main - release/* workflow_dispatch: inputs: python_v: description: 'python version' required: true default: '3.11' type: choice options: - '3.10' - '3.11' - '3.12' - '3.13' - '3.14' env: PYTHON_V: ${{ github.event.inputs.python_v || '3.11' }} jobs: perceval-autotests: name: Run PyTest on ${{ matrix.os }} if: always() runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-latest, windows-latest, ubuntu-latest] steps: - if: runner.os == 'Linux' name: Initialize PYTHON_V_CP linux run: | echo "PYTHON_V_CP=cp$( echo '${{env.PYTHON_V}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV - if: runner.os != 'Linux' name: Initialize PYTHON_V_CP notLinux run: | echo "PYTHON_V_CP=cp$( echo '${{env.PYTHON_V}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV shell: Bash - uses: actions/checkout@v4 - name: Set up Python ${{ env.PYTHON_V }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_V }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install . python -m pip install -r tests/requirements.txt - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --ignore=F824 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pytest perceval-lite-autotests: name: Run PyTest with light install on ${{ matrix.os }} if: always() runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest] steps: - if: runner.os == 'Linux' name: Initialize PYTHON_V_CP linux run: | echo "PYTHON_V_CP=cp$( echo '${{env.PYTHON_V}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV - if: runner.os != 'Linux' name: Initialize PYTHON_V_CP notLinux run: | echo "PYTHON_V_CP=cp$( echo '${{env.PYTHON_V}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV shell: Bash - uses: actions/checkout@v4 - name: Set up Python ${{ env.PYTHON_V }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_V }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install . python -m pip install -r tests/requirements.txt python -m pip uninstall -y -r .github/rendering_requirements.txt - name: Test with pytest run: | pytest -m "not rendering" perceval-docs-test: name: Build docs on ubuntu needs: - perceval-autotests runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: - if: runner.os == 'Linux' name: Initialize PYTHON_V_CP linux run: | echo "PYTHON_V_CP=cp$( echo '${{env.PYTHON_V}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV - if: runner.os != 'Linux' name: Initialize PYTHON_V_CP notLinux run: | echo "PYTHON_V_CP=cp$( echo '${{env.PYTHON_V}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV shell: Bash - uses: actions/checkout@v4 with: submodules: 'recursive' fetch-tags: true fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_V }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install -r docs/requirements.txt python -m pip install . - name: Install Pandoc run: | sudo apt update sudo apt install pandoc - name: Build docs run: | cd docs make html - name: 'Create docs Artefact' uses: actions/upload-artifact@v4 with: name: docs path: docs/build/html retention-days: 1 ================================================ FILE: .github/workflows/benchmarks.yml ================================================ # this is a manual workflow that compare benchmarks with the same runner and the same version of python # choose runner, perceval ref, save data and log # runs benchmarks at benchmarks/benchmark_*.py name: Benchmarks on: workflow_dispatch: inputs: runner: description: Runner required: true default: 'MiniMac_arm64' type: choice options: - MiniMac_arm64 - ubuntu-latest commit_ref: description: Use specific perceval's ref (branch, tag or SHA) default: '' type: string required: false save: description: save in the current github repository default: false required: false type: boolean gh_branch: description: Use specific branch for save graph default: 'main' type: string required: false jobs: setup: runs-on: ubuntu-latest outputs: save: ${{ steps.step1.outputs.save }} runner: ${{ steps.step1.outputs.runner }} os: ${{ steps.step1.outputs.os }} commit_ref: ${{ steps.step1.outputs.commit_ref }} python_v: ${{ steps.step1.outputs.python_v }} python_v_cp: ${{ steps.step1.outputs.python_v_cp }} folder_env: ${{ steps.step1.outputs.folder_env }} folder_file_json: ${{ steps.step1.outputs.folder_file_json }} gh_branch: ${{ steps.step1.outputs.gh_branch }} steps: - name: Check branch id: step1 run: | echo "save=${{ github.event.inputs.save == null || github.event.inputs.save }}" >> $GITHUB_OUTPUT echo "runner=${{ github.event.inputs.runner || 'MiniMac_arm64' }}" >> $GITHUB_OUTPUT if [ ${{ github.event.inputs.runner }} == 'ubuntu-latest' ]; then echo "os=ubuntu-latest" >> $GITHUB_OUTPUT else echo "os=macos-latest" >> $GITHUB_OUTPUT fi echo "commit_ref=${{ github.event.inputs.commit_ref || '' }}" >> $GITHUB_OUTPUT echo "python_v=${{ github.event.inputs.python_v || '3.11' }}" >> $GITHUB_OUTPUT echo "python_v_cp=cp$( echo '${{github.event.inputs.python_v || '3.11'}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_OUTPUT echo "folder_env=${{ github.event.inputs.os || 'MiniMac_arm64' }}-CPython-${{ github.event.inputs.python_v || '3.11' }}" >> $GITHUB_OUTPUT echo "folder_file_json=.benchmarks/${{ github.event.inputs.os || 'MiniMac_arm64' }}-CPython-${{ github.event.inputs.python_v || '3.11' }}/log/${{ github.run_number }}_$( git describe --tags )_${{ github.sha }}.json" >> $GITHUB_OUTPUT echo "gh_branch=${{ github.event.inputs.gh_branch || 'main' }}" >> $GITHUB_OUTPUT benchmark: name: Run pytest-benchmark benchmark example if: ${{ always() }} needs: - setup runs-on: ${{ needs.setup.outputs.runner }} steps: - name: checkout on perceval's ref uses: actions/checkout@v4 with: ref: ${{ needs.setup.outputs.commit_ref }} fetch-depth: 0 # install python, already DL on MiniMac_arm64 - if: ${{ needs.setup.outputs.runner != 'MiniMac_arm64' }} name: setup python uses: actions/setup-python@v5 with: python-version: ${{ needs.setup.outputs.python_v }} - name: setup virtual env uses: syphar/restore-virtualenv@v1 id: cache-virtualenv - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install . python -m pip install -r tests/requirements.txt - name: Run benchmark run: | python -m pytest benchmark/benchmark_*.py --benchmark-json out.json --benchmark-storage file://./.benchmarks/${{ needs.setup.outputs.folder_env }}/log/ mv out.json ${{ needs.setup.outputs.folder_file_json }} # upload the result on action GitHub - name: upload the log result uses: actions/upload-artifact@v4 with: name: pytest_benchmarks_log_${{ needs.setup.outputs.folder_env }}_${{ github.run_number }} path: ${{ needs.setup.outputs.folder_file_json }} - name: checkout in Initial commit to avoid bug uses: actions/checkout@v4 with: ref: 217f0c716956da75eac217e9bc089f881bd5a2aa - name: Download the log result uses: actions/download-artifact@v3 with: name: pytest_benchmarks_log_${{ needs.setup.outputs.folder_env }}_${{ github.run_number }} path: .benchmarks/${{ needs.setup.outputs.folder_env }}/log # use github-action-benchmark for graph - if: ${{ needs.setup.outputs.save == 'true' }} name: create a graph and save on current repo uses: benchmark-action/github-action-benchmark@v1 with: name: Automated benchmarks report tool: 'pytest' output-file-path: ${{ needs.setup.outputs.folder_file_json }} benchmark-data-dir-path: .benchmarks/${{ needs.setup.outputs.folder_env }} gh-pages-branch: ${{ needs.setup.outputs.gh_branch }} github-token: ${{ secrets.GITHUB_TOKEN }} auto-push: true alert-threshold: '120%' comment-on-alert: true - if: ${{ github.event_name == 'workflow_dispatch' && needs.setup.outputs.save == 'false' }} name: create a graph and save on private repo uses: benchmark-action/github-action-benchmark@v1 with: name: Automated benchmarks report tool: 'pytest' output-file-path: ${{ needs.setup.outputs.folder_file_json }} benchmark-data-dir-path: .benchmarks/${{ needs.setup.outputs.folder_env }} gh-pages-branch: ${{ needs.setup.outputs.gh_branch }} github-token: ${{ secrets.PERCEVAL_BENCHMARK_TOKEN }} gh-repository: 'github.com/Quandela/Perceval-PrivateBenchmark' auto-push: true alert-threshold: '120%' comment-on-alert: true - uses: actions/checkout@v4 with: repository: Quandela/Perceval-PrivateBenchmark ref: ${{ needs.setup.outputs.gh_branch }} path: Perceval-PrivateBenchmark token: ${{ secrets.PERCEVAL_BENCHMARK_TOKEN }} submodules: recursive fetch-depth: 0 - name: upload the log result uses: actions/upload-artifact@v4 with: name: benchmarks_graph path: Perceval-PrivateBenchmark/.benchmarks/${{ needs.setup.outputs.folder_env }}/ - name: Download the log result uses: actions/download-artifact@v3 with: name: benchmarks_graph path: Perceval-PrivateBenchmark/.benchmarks/${{ needs.setup.outputs.folder_env }}/ - name: Install SSH Key uses: shimataro/ssh-key-action@v2 with: key: ${{ secrets.PERCEVAL_WEB_SSHKEY }} known_hosts: ${{ secrets.PERCEVAL_WEB_KNOWN_HOST }} - name: Deploy run: rsync -avz Perceval-PrivateBenchmark/.benchmarks/${{ needs.setup.outputs.folder_env }}/ ${{secrets.PERCEVAL_WEB_USER}}@${{secrets.PERCEVAL_WEB_HOST}}:/var/www/html-benchmark ================================================ FILE: .github/workflows/build-and-deploy-docs.yml ================================================ # This is a basic workflow that is manually triggered name: Build and deploy docs # Controls when the action will run. Workflow runs when manually triggered using the UI # or API. on: release: types: [published] workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: deploy-docs: name: Build and deploy docs to website # The type of runner that the job will run on runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: - uses: actions/checkout@v4 with: submodules: recursive fetch-tags: true fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install -r docs/requirements.txt python -m pip install . - name: Install Pandoc run: | sudo apt update sudo apt install pandoc - name: Build docs run: | cd docs make multiversion - name: Install SSH Key uses: shimataro/ssh-key-action@v2 with: key: ${{ secrets.PERCEVAL_WEB_SSHKEY }} known_hosts: ${{ secrets.PERCEVAL_WEB_KNOWN_HOST }} - name: Deploy run: rsync -avz --delete docs/build/html/ ${{secrets.PERCEVAL_WEB_USER}}@${{secrets.PERCEVAL_WEB_HOST}}:/var/www/html-perceval ================================================ FILE: .github/workflows/python-publish.yml ================================================ # This workflow will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries # This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. name: Deploy to PyPi on: release: types: [published] workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install wheel scmver setuptools - name: Build package run: python setup.py sdist bdist_wheel - name: Publish package uses: pypa/gh-action-pypi-publish@v1.12.4 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} ================================================ FILE: .github/workflows/rerun-notebooks-and-build-docs.yml ================================================ # This is a basic workflow that is manually triggered name: Rerun notebooks and build docs # Controls when the action will run. Workflow runs when manually triggered using the UI # or API. on: pull_request: branches: - main workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: perceval-extensive-autotests: name: Run PyTest on ${{ matrix.os }} and with python version ${{ matrix.version }} if: always() runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-latest, windows-latest, ubuntu-latest] version: ['3.10', '3.14'] # only running with oldest and newest version steps: - if: runner.os == 'Linux' name: Initialize PYTHON_V_CP linux run: | echo "PYTHON_V_CP=cp$( echo '${{matrix.version}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV - if: runner.os != 'Linux' name: Initialize PYTHON_V_CP notLinux run: | echo "PYTHON_V_CP=cp$( echo '${{matrix.version}}' | sed 's/\.\([0-9]\)/\1/' )" >> $GITHUB_ENV shell: Bash - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.version }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install . python -m pip install -r tests/requirements.txt - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --ignore=F824 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | pytest build-env: name: Rerun notebooks and build docs # The type of runner that the job will run on runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: - uses: actions/checkout@v4 with: submodules: recursive fetch-tags: true fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: # Documentation needs 3.11 to be built python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install . python -m pip install -r docs/requirements.txt -r docs/source/notebooks/requirements.txt - name: Rerun notebooks run: | ./.github/workflows/rerun_notebooks.sh - name: Install Pandoc run: | sudo apt update sudo apt install pandoc - name: Build docs run: | cd docs make multiversion - name: 'Create docs Artefact' uses: actions/upload-artifact@v4 with: name: docs path: docs/build/html retention-days: 1 ================================================ FILE: .github/workflows/rerun_notebooks.sh ================================================ my_function () { echo converting $notebook jupyter nbconvert --clear-output --inplace "$notebook" jupyter nbconvert --execute --to notebook --inplace "$notebook" jupyter nbconvert --ClearMetadataPreprocessor.enabled=True --inplace "$notebook" } nb_dir="docs/source/notebooks" for entry in `ls $nb_dir | grep \.ipynb`; do notebook=$nb_dir/$entry if [[ $notebook =~ ".ipynb" ]] then if [ "$notebook" = "docs/source/notebooks/BS-based_implementation.ipynb" ] then echo $notebook is ignore elif [ "$notebook" = "docs/source/notebooks/Boson_sampling.ipynb" ] then echo $notebook is ignore elif [ "$notebook" = "docs/source/notebooks/Gedik_qudit.ipynb" ] then echo $notebook is ignore elif [ "$notebook" = "docs/source/notebooks/Remote_Computation_Tutorial.ipynb" ] then echo $notebook is ignore else my_function fi fi done ================================================ FILE: .gitignore ================================================ .pytest_cache __pycache__ perceval_quandela.egg-info build .eggs/ dist/ .idea/ venv* *.whl .ipynb_checkpoints/ .coverage .vscode* .venv ================================================ FILE: .pre-commit-config.yaml ================================================ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.2.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.1.13 hooks: - id: forbid-crlf types: ["file", "python", "text"] - id: remove-crlf types: ["file", "python", "text"] - id: forbid-tabs types: ["file", "python", "text"] - id: remove-tabs types: ["file", "python", "text"] - id: insert-license types: ["file", "python", "text"] args: - --license-filepath - LICENSE - repo: local hooks: - id: jupyter-nb-clear-metadata name: jupyter-nb-clear-metadata files: \.ipynb$ stages: [pre-commit] language: python entry: jupyter nbconvert --ClearMetadataPreprocessor.enabled=True --inplace ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at perceval.oss@quandela.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing *Thanks for being there!* There are many ways you can help in the Perceval project. This document will guide you through this process. ## Reporting issues We use GitHub issues for bugs in the code that are **reproducible**. A good bug report should contain every information needed to reproduce it. Before opening a new issue, make sure to: * **use the GitHub issue search** for existing and fixed bugs; * **check if the issue has been fixed** in a more recent version; * **isolate the problem** to give as much context as possible. If you have questions on how to use the project or have trouble getting started with it, consider using [our forum](https://perceval.quandela.net/forum/) instead. ## Requesting features Do you think a feature is missing or would be a great addition to the project? Please open a GitHub issue to describe it. ## Contributing to the documentation We would like the documentation to be as complete as possible, providing details about the framework, but also about the field - and serve as a fully consistent training material. From our experience, documentation is never an achieved task - and feel free to contribute - all the documentation is part of the project in https://github.com/Quandela/Perceval/source. ## Developing code *You want to share some code, that's great!* * If you want to contribute with code but are unsure what to do, * search for *TODO* comments in the code: these are small dev tasks that should be addressed at some point. * look for GitHub issues marked with the *help wanted* label: these are developments that we find particularly suited for community contributions. * If you are planning to make a large change to the existing code, consider asking first on [the forum](https://perceval.quandela.net/forum/) to confirm that it is welcome. In any cases, your new code **must**: * pass the existing tests * pass the [`flake8`](https://flake8.pycqa.org/en/latest/) style checker and **should**: * add new tests → you can use the coverage report to help you find untested code (see [README](README.md#running-tests-and-benchmarks) ) * update the [documentation](docs/source/) ## Helping others Sharing experiences on the [forum](https://perceval.quandela.net/forum/), or contributing to open discussions will be highly appreciated! ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 Quandela Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. As a special exception, the copyright holders of exqalibur library give you permission to combine exqalibur with code included in the standard release of Perceval under the MIT license (or modified versions of such code). You may copy and distribute such a combined system following the terms of the MIT license for both exqalibur and Perceval. This exception for the usage of exqalibur is limited to the python bindings used by Perceval. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![GitHub release](https://img.shields.io/github/v/release/Quandela/Perceval.svg?style=plastic)](https://github.com/Quandela/Perceval/releases/latest) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/perceval-quandela?style=plastic) [![CI](https://github.com/Quandela/Perceval/actions/workflows/python-publish.yml/badge.svg)](https://github.com/Quandela/Perceval/actions/workflows/python-publish.yml) [![CI](https://github.com/Quandela/Perceval/actions/workflows/autotests.yml/badge.svg)](https://github.com/Quandela/Perceval/actions/workflows/autotests.yml) [![CI](https://github.com/Quandela/Perceval/actions/workflows/build-and-deploy-docs.yml/badge.svg)](https://github.com/Quandela/Perceval/actions/workflows/build-and-deploy-docs.ym) # Perceval Through a simple object-oriented python API, Perceval provides tools for building a circuit with linear optics components, defining single-photon sources and their error model, manipulating Fock states, running simulations, reproducing published experimental papers results, and experimenting with a new generation of quantum algorithms. It is interfaced with the available QPUs on the [Quandela cloud](https://cloud.quandela.com/webide/), so it is possible to run computations on an actual photonic computer. Perceval aims to be a companion tool for developing discrete-variable photonics circuits - while simulating their design, modelling their ideal and real-life behaviour; - and proposing a normalized interface to control photonic quantum computers; - while using powerful simulation backends to get state-of-the-art simulation; - and also allowing direct access to the QPUs of Quandela. Perceval has been developed as a complete toolkit for physicists and quantum computational students, researchers and practitioners. # Key Features * Powerful Circuit designer making use of predefined components * Simple python API and powerful simulation backends optimized in C * Misc technical utilities to manipulate State Vector, Unitary Matrices, and circuit Parameters * Transversal tools for visualization compatible with notebooks or local development environments * Numerical and symbolical computation * Modular architecture welcoming contributions from the community # Installation Perceval requires: * Python between 3.10 and 3.14 ## PIP We recommend installing it with `pip`: ```bash pip install --upgrade pip pip install perceval-quandela ``` Our qiskit, qutip, cqasm or myqlm bridges have been moved to a separate interoperability package `perceval-interop` (https://perceval.quandela.net/interopdocs/). ## GitHub ```bash git clone https://github.com/quandela/Perceval ``` then to install Perceval: ```bash pip install . ``` Or for developers: ```bash pip install -e . ``` # Running tests and benchmarks Unit tests files are part of the repository in `tests/` and can be run with: ``` pip install -r tests/requirements.txt pytest ``` Additionally, you can see a coverage report with the command: ``` pytest --cov=perceval ``` Benchmark tests for computing-intensive functions are in `benchmark/` and can be run with: ``` pytest benchmark/benchmark_*.py ``` Comparison benchmarks for different platforms are also committed in `.benchmarks/` - see [pytest-benchmark documentation](https://pytest-benchmark.readthedocs.io/en/stable/usage.html) for more information. # Documentation and Forum * The [documentation](https://perceval.quandela.net/docs) * The [Community Forum](https://perceval.quandela.net/forum) # [](https://www.quandela.com/) [![Twitter Follow](https://img.shields.io/twitter/follow/Quandela_SAS?style=social)](https://twitter.com/Quandela_SAS) [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UCl5YMpSqknJ1n-IT-XWfLsQ?style=social)](https://www.youtube.com/channel/UCl5YMpSqknJ1n-IT-XWfLsQ) ================================================ FILE: benchmark/benchmark-circuit-building.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import perceval as pcvl from perceval.components.unitary_components import BS, PS, PERM import random import time t_build = 0 t_set = 0 t_compute = 0 t_get = 0 for _ in range(1000): start = time.time() #List of the parameters φ1,φ2,...,φ8 List_Parameters = [] # VQE is a 6 optical mode circuit VQE = pcvl.Circuit(6) VQE.add((1, 2), BS.H()) VQE.add((3, 4), BS.H()) List_Parameters.append(pcvl.Parameter("φ1")) VQE.add((2,), PS(phi=List_Parameters[-1])) List_Parameters.append(pcvl.Parameter("φ3")) VQE.add((4,), PS(phi=List_Parameters[-1])) VQE.add((1, 2), BS.H()) VQE.add((3, 4), BS.H()) List_Parameters.append(pcvl.Parameter("φ2")) VQE.add((2,), PS(phi=List_Parameters[-1])) List_Parameters.append(pcvl.Parameter("φ4")) VQE.add((4,), PS(phi=List_Parameters[-1])) # CNOT ( Post-selected with a success probability of 1/9) VQE.add([0,1,2,3,4,5], PERM([0,1,2,3,4,5]))#Identity PERM (permutation) for the purpose of drawing a nice circuit VQE.add((3, 4), BS.H()) VQE.add([0,1,2,3,4,5], PERM([0,1,2,3,4,5]))#Identity PERM (permutation) for the same purpose VQE.add((0, 1), BS.H(theta=BS.r_to_theta(1/3))) VQE.add((2, 3), BS.H(theta=BS.r_to_theta(1/3))) VQE.add((4, 5), BS.H(theta=BS.r_to_theta(1/3))) VQE.add([0,1,2,3,4,5], PERM([0,1,2,3,4,5]))#Identity PERM (permutation) for the same purpose VQE.add((3, 4), BS.H()) VQE.add([0,1,2,3,4,5], PERM([0,1,2,3,4,5]))#Identity PERM (permutation) for the same purpose List_Parameters.append(pcvl.Parameter("φ5")) VQE.add((2,), PS(phi=List_Parameters[-1])) List_Parameters.append(pcvl.Parameter("φ7")) VQE.add((4,), PS(phi=List_Parameters[-1])) VQE.add((1, 2), BS.H()) VQE.add((3, 4), BS.H()) List_Parameters.append(pcvl.Parameter("φ6")) VQE.add((2,), PS(phi=List_Parameters[-1])) List_Parameters.append(pcvl.Parameter("φ8")) VQE.add((4,), PS(phi=List_Parameters[-1])) VQE.add((1, 2), BS.H()) VQE.add((3, 4), BS.H()) t_build += time.time()-start start = time.time() init_param = [random.random() for _ in List_Parameters] for idx, p in enumerate(List_Parameters): p.set_value(init_param[idx]) t_set += time.time()-start start = time.time() VQE.compute_unitary(use_symbolic = False) t_compute += time.time()-start start = time.time() for i in range(len(List_Parameters)): init_param[i] = VQE.get_parameters()[i]._value t_get += time.time()-start print("TOTAL=", t_build+t_set+t_compute+t_get, "DETAIL=", t_build, t_set, t_compute, t_get) ================================================ FILE: benchmark/benchmark-unitary.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. r""" This script compares building of unitary when using Circuit or directly by building matrix. """ import perceval as pcvl import perceval.components.unitary_components as comp import time import numpy as np m = 8 def phase_shift(n_mode, theta): # phase shift in m x m unitary in mode 1 of angle theta ps_matrix = np.eye(n_mode, dtype=complex) ps_matrix[0, 0] = np.cos(theta) + 1j * np.sin(theta) return ps_matrix u1 = pcvl.Matrix.random_unitary(m) u2 = pcvl.Matrix.random_unitary(m) px = pcvl.P("x") c = comp.Unitary(u2) // (0, comp.PS(px)) // comp.Unitary(u1) dt_circuit = 0 dt_raw = 0 for _ in range(1000): top0 = time.time_ns() px.set_value(1) c.compute_unitary(use_symbolic=False) top1 = time.time_ns() dt_circuit += top1-top0 top0 = time.time_ns() U = u1 @ phase_shift(m, 1) @ u2 top1 = time.time_ns() dt_raw += top1-top0 if dt_circuit/dt_raw > 2.5: print("TOO_SLOW", "circuit", dt_circuit, "raw", dt_raw, "factor", dt_circuit/dt_raw) else: print("OK", "circuit", dt_circuit, "raw", dt_raw, "factor", dt_circuit/dt_raw) ================================================ FILE: benchmark/benchmark_QML-DE-solver.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import random import perceval as pcvl import perceval.components.unitary_components as comp import numpy as np from math import comb from scipy.optimize import minimize # Differential equation parameters lambd = 8 kappa = 0.1 def F(u_prime, u, x): # DE: F(u_prime, u, x) = 0 # Must work with numpy arrays return (u_prime + lambd * u * (kappa + np.tan(lambd * x))) # Boundary condition (f(x_0)=f_0) x_0 = 0 f_0 = 1 # Modeling parameters n_grid = 50 # number of grid points of the discretized differential equation range_min = 0 # minimum of the interval on which we wish to approximate our function range_max = 1 # maximum of the interval on which we wish to approximate our function X = np.linspace(range_min, range_max, n_grid) # Optimisation grid # Parameters of the quantum machine learning procedure eta = 5 # weight granted to the initial condition a = 200 # Approximate boundaries of the interval that the image of the trial function can cover N = m = 6 N2 = N ** 2 input_state = pcvl.BasicState([1] * N + [0] * (m - N)) s1 = pcvl.SLOSBackend() s1.set_circuit(pcvl.Unitary(pcvl.Matrix.random_unitary(m))) s1.preprocess([input_state]) random.seed(0) np.random.seed(0) pcvl.random_seed(0) fock_dim = comb(N + m - 1, N) lambda_random = np.random.rand(fock_dim) lambda_random = a * (lambda_random - np.mean(lambda_random)) / np.std(lambda_random) dx = (range_max - range_min) / (n_grid - 1) parameters = np.random.normal(size=4 * N2) def calc(circuit, input_state, coefs): s1.set_circuit(circuit) probs = s1.all_prob(input_state) return np.sum(np.multiply(probs, coefs)) def computation(params): """compute the loss function of a given differential equation in order for it to be optimized""" coefs = lambda_random # coefficients of the M observable # initial condition with the two universal interferometers and the phase shift in the middle U_1 = pcvl.Matrix.parametrized_unitary(N, params[:2 * N2]) U_2 = pcvl.Matrix.parametrized_unitary(N, params[2 * N2:4 * N2]) # Circuit creation px = pcvl.P("px") c = (comp.Unitary(U_2) // (0, comp.PS(px)) // comp.Unitary(U_1)) px.set_value(x_0) f_theta_0 = calc(c, input_state, coefs) # boundary condition given a weight eta loss = eta * (f_theta_0 - f_0) ** 2 * n_grid # Warning : Y[0] is before the domain we are interested in (used for differentiation), the domain begins at Y[1] Y = np.zeros(n_grid + 2) # Small optimisation working if x_0 == range_min if x_0 == range_min: Y[1] = f_theta_0 assigned = 1 else: assigned = 0 px.set_value(range_min - dx) Y[0] = calc(c, input_state, coefs) for i in range(assigned, n_grid): x = X[i] px.set_value(x) Y[i + 1] = calc(c, input_state, coefs) # Y_prime[0] is the beginning of the domain /!\ not the same for Y px.set_value(range_max + dx) Y[n_grid + 1] = calc(c, input_state, coefs) Y_prime = (Y[2:] - Y[:-2]) / (2 * dx) # This method is apparently the fastest to calculate the L2 norm squared loss += np.sum((F(Y_prime, Y[1:-1], X)) ** 2) current_loss = loss / n_grid return current_loss def test_QML_DE_solver(benchmark): benchmark(minimize, computation, parameters, method='BFGS', options={'gtol': 1E-2}) ================================================ FILE: benchmark/benchmark_bosonsampling.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import perceval as pcvl from perceval.components.unitary_components import BS, PS, Unitary def get_interferometer(n): def _gen_mzi(i: int): return pcvl.catalog["mzi phase last"].build_circuit(theta_a=0.42, theta_b=0.42, phi_a=math.pi+i*0.1, phi_b=math.pi/2) return pcvl.GenericInterferometer(n, _gen_mzi) def simulate_sampling(shots, circuit, input_state): clifford = pcvl.Clifford2017Backend() clifford.set_circuit(circuit) clifford.set_input_state(input_state) for i in range(shots): clifford.sample() def test_bosonsampling_clifford_6(benchmark): benchmark(simulate_sampling, shots=100, circuit=get_interferometer(6), input_state=pcvl.BasicState([1] * 6)) def test_bosonsampling_clifford_8(benchmark): benchmark(simulate_sampling, shots=20, circuit=get_interferometer(8), input_state=pcvl.BasicState([1] * 8)) ================================================ FILE: benchmark/benchmark_pdisplay.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. r""" This script times the execution of pdisplay for circuits. """ import perceval as pcvl import perceval.components.unitary_components as comp trials = 2 def generate_circuit(n_mode): u = pcvl.Matrix.random_unitary(n_mode) mzi = (pcvl.Circuit(2) // comp.BS() // (0, comp.PS(phi=pcvl.P("φ_a"))) // comp.BS() // (0, comp.PS(phi=pcvl.P("φ_b")))) return pcvl.Circuit.decomposition(u, mzi, shape=pcvl.InterferometerShape.TRIANGLE) c6 = generate_circuit(6) c12 = generate_circuit(12) def run_pdisplay(c, t, f): for _ in range(t): pcvl.pdisplay(c, output_format=f, mplot_noshow=True) def _run_pdisplay_mplot_6(): run_pdisplay(c6, trials, pcvl.Format.MPLOT) def _run_pdisplay_mplot_12(): run_pdisplay(c12, trials, pcvl.Format.MPLOT) def _run_pdisplay_svg_6(): run_pdisplay(c6, trials, pcvl.Format.HTML) def _run_pdisplay_svg_12(): run_pdisplay(c12, trials, pcvl.Format.HTML) def test_pdisplay_mplot_6(benchmark): benchmark(_run_pdisplay_mplot_6) def test_pdisplay_mplot_12(benchmark): benchmark(_run_pdisplay_mplot_12) def test_pdisplay_svg_6(benchmark): benchmark(_run_pdisplay_svg_6) def test_pdisplay_svg_12(benchmark): benchmark(_run_pdisplay_svg_12) ================================================ FILE: benchmark/benchmark_stepper.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import random import perceval as pcvl from perceval.backends import SLOSBackend, NaiveBackend from perceval.simulators import Stepper from perceval.components import BS, PS from perceval.utils import BasicState # definition of the circuit C = pcvl.Circuit(2) C.add((0, 1), BS()) C.add(1, PS(1)) C.add((0, 1), BS()) N = 100 def get_sample_from_statevector(sv): p = random.random() state = None for state, pa in sv: proba = abs(pa)**2 if p > proba: p -= proba continue break return state def run_stepper(): samples = [] stepper = Stepper(SLOSBackend()) stepper.set_circuit(C) for i in range(N): sv = pcvl.StateVector(pcvl.BasicState([1, 0])) for r, c in C: sv = stepper.apply(sv, r, c) samples.append(get_sample_from_statevector(sv)) return samples def run_direct(): bs = C._components[0][1] sim_bs = NaiveBackend() sim_bs.set_circuit(bs) ps = C._components[1][1] sim_ps = NaiveBackend() sim_ps.set_circuit(ps) samples = [] bs10 = BasicState([1,0]) bs01 = BasicState([0,1]) for i in range(N): # apply first bs sim_bs.set_input_state(bs10) sv_a0 = sim_bs.prob_amplitude(bs10) sv_a1 = sim_bs.prob_amplitude(bs01) # apply ps sv_b0 = sv_a0 sim_ps.set_input_state(BasicState([1])) sv_b1 = sv_a1*sim_ps.prob_amplitude(BasicState([1])) # apply second bs sv_c0 = sv_b0*sim_bs.prob_amplitude(bs10) sv_c1 = sv_b0*sim_bs.prob_amplitude(bs01) sim_bs.set_input_state(bs01) sv_c0 += sv_b1*sim_bs.prob_amplitude(bs10) sv_c1 += sv_b1*sim_bs.prob_amplitude(bs01) # sampling from there samples.append(bs10 if random.random() > abs(sv_c0)**2 else bs01) return samples def test_stepper(benchmark): benchmark(run_stepper) def test_stepper_comp_direct(benchmark): benchmark(run_direct) ================================================ FILE: conftest.py ================================================ import pytest def pytest_addoption(parser): parser.addoption("--skip-long-test", action="store_true", help="do not run long tests") parser.addoption("--save_figs", action="store_true", help="use to generate new test figures") def pytest_runtest_setup(item): if 'long_test' in item.keywords and item.config.getoption("--skip-long-test"): pytest.skip("ignored per user request") ================================================ FILE: docs/Makefile ================================================ # Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) multiversion: python -m sphinx_multiversion -f multiversion_config source build/html clean: rm -rf build .PHONY: clean ================================================ FILE: docs/make.bat ================================================ @ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd ================================================ FILE: docs/multiversion_config/conf.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys import re from pathlib import Path from git import Repo from perceval.utils.versions.version_utils import keep_latest_versions sys.path.insert(0, os.path.relpath("../")) REPO_PATH = Path(__file__).parent.parent.parent.resolve() repo = Repo(REPO_PATH) tags = [tag.name for tag in repo.tags] versions = keep_latest_versions(tags, "v0.6") versions_string = "|".join([f"({one_version})" for one_version in versions]) versions_regex = re.compile(f"^{versions_string}$") print(f"Building {versions_regex}") # Whitelist pattern for tags (set to None to ignore all tags) smv_tag_whitelist = versions_regex # Whitelist pattern for branches (set to None to ignore all branches) smv_branch_whitelist = None # Whitelist pattern for remotes (set to None to use local branches only) smv_remote_whitelist = None # Pattern for released versions smv_released_pattern = r".*" smv_regex_name = r"(.*)\..*" ================================================ FILE: docs/requirements.txt ================================================ wheel sphinx==6.2.1 sphinx_rtd_theme sphinxcontrib-bibtex sphinx_autodoc_typehints enum-tools[sphinx] nbsphinx jinja2==3.0.0 ipython ipykernel gitpython git+https://github.com/Quandela/sphinx-multiversion-contrib.git@1.1.1 ================================================ FILE: docs/source/_static/css/style.css ================================================ @import url("theme.css"); .wy-nav-content { max-width: 1200px !important; } ================================================ FILE: docs/source/_templates/layout.html ================================================ {% extends "!layout.html" %} {% block extrahead %} {{ super() }} {% endblock %} {% block extrabody %} {{ super() }} {% endblock %} ================================================ FILE: docs/source/_templates/page.html ================================================ {% extends "!page.html" %} {% block body %} {% if current_version and latest_version and current_version != latest_version %}

You're reading the documentation of the {{current_version.name}}. For the latest released version, please have a look at {{latest_version.name}}.

{% endif %} {{ super() }} {% endblock %}% ================================================ FILE: docs/source/_templates/versions.html ================================================ {%- if current_version %}
Other Versions v: {{ current_version.name }}
{%- if versions.tags %}
Tags
{%- for item in versions.tags %}
{{ item.name }}
{%- endfor %}
{%- endif %} {%- if versions.branches %}
Branches
{%- for item in versions.branches %}
{{ item.name }}
{%- endfor %}
{%- endif %}
{%- endif %} ================================================ FILE: docs/source/backends.rst ================================================ Simulation Back-ends ==================== To run a simulation, computing back-ends implemented from state of the art algorithms are available in Perceval. Each of these back-ends has different capabilities that we describe in that section. All Perceval simulation back-ends act on perfect input Fock states on a fixed unitary circuit. Nonetheless, Perceval aims at supporting noisy and non-unitary simulations. These real life use cases are covered in the next part of the tutorial. I. Back-end features -------------------- Sampling a.k.a Weak Simulation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Sampling is the simulation task closest to the actual running of a physical circuit. Given known input states, the sampling will produce output states one at a time as it would be observed by ideal detectors. Sampling is considered as a weak simulation of a circuit, since it does not give explicit output distribution, nor the nature of the mixture states generated by the circuit but a mere observation of individual outputs. Sampling has been studied in length (see :cite:t:`clifford_classical_2018`, :cite:t:`clifford2020faster`), in the context of Boson Sampling, as a classical computing challenge pushing further the limit of the quantum supremacy. Strong Simulation ^^^^^^^^^^^^^^^^^ Strong(er) Simulation (see :cite:t:`heurtel2022`) of a circuit should provide an access to complete distribution and nature of the output state. Compared to *Sampling*, circuit designer are interested in the actual probabilistic distribution of the outputs and their exact characteristic. In particular, for a :math:`m` port circuit, one would like to know the exact expected probability of detecting photons on a given port, and not a mere estimation based on sampling observations. We also differentiate here the ability of getting the probability (or probability amplitude) of a single output, and the possibilities of getting probabilities of all the different outputs for a give input state. Also, even for a simple circuit showing an equiprobable probability of detecting :math:`\ket{0,1}` and :math:`\ket{1,0}`, we would want to know if the output is :math:`\frac{1}{\sqrt 2}(\ket{0,1}+\ket{1,0})` or :math:`\frac{1}{\sqrt 2}(\ket{0,1}-\ket{1,0})` which are very distinct states. Finally, a fine-grained simulation would need not only to give output state probability but also *probability amplitude*. Indeed, probability amplitude is required for further evolution of the output states, but also analysis of polarization for circuit with polarization support, etc. II. Back-end comparison ----------------------- Perceval has 6 different built-in back-ends with the support of optimized C++ library. Comparison Table ^^^^^^^^^^^^^^^^ .. list-table:: :header-rows: 1 :stub-columns: 1 :width: 100% :align: center * - Features \ Name - :ref:`CliffordClifford2017` - :ref:`SLOS` - :ref:`Naive` - :ref:`NaiveApprox` - :ref:`MPS` - :ref:`SLAP` * - Sampling Efficiency - :math:`\mathrm{O}(n2^n+poly(m,n))` - :math:`\mathrm{O}(mC_n^{n+m-1})` - *N/A* [1]_ - *N/A* [1]_ - *N/A* [1]_ - Theoretically :math:`\mathrm{O}(n2^n+poly(m,n))` * - Single output Efficiency - *N/A* - *N/A* - :math:`\mathrm{O}(n2^n)` - :math:`\mathrm{O}(n)` - :math:`\mathrm{O}(N_cC_n^{n+m-1})` - :math:`\mathrm{O}(n2^n)` * - Full Distribution Efficiency - *N/A* - :math:`\mathrm{O}(nC_n^{n+m-1})` - :math:`\mathrm{O}(n2^nC_n^{n+m-1})` - :math:`\mathrm{O}(nC_n^{n+m-1})` - :math:`\mathrm{O}(N_cC_n^{n+m-1})` - :math:`\mathrm{O}(\begin{equation} 2n\times \sum_{k=1}^n \binom{n-1}{k-1} \times \binom{m+k-1}{m-1} \label{eq:complex} \end{equation})` * - Probability Amplitude - **No** - **Yes** - **Yes** - **Yes** - **Yes** - **Yes** * - Practical Limits - :math:`n\approx30` - :math:`n,m<20` - :math:`n\approx30` - - - :math:`n,m<20` where: * :math:`n` is the number of photons * :math:`m` is the number of modes * :math:`N_c` is the number of elementary circuits CliffordClifford2017 ^^^^^^^^^^^^^^^^^^^^ This backend is the implementation of the algorithm introduced in :cite:t:`clifford_classical_2018`. The algorithm, applied to Boson Sampling, aims to *produce provably correct random samples from a particular quantum mechanical distribution*. Its time and space complexity are respectively :math:`\mathrm{n2^n+mn^2}` and :math:`\mathrm{m}` (in addition to matrix storing). The algorithm has been implemented in C++, and uses an adapted Glynn algorithm :cite:t:`glynn2010permanent` to efficiently compute :math:`n` simultaneous *sub-permanents*. Recently, the same authors have proposed a faster algorithm in :cite:t:`clifford2020faster` with an average time complexity of :math:`\mathrm{n\rho_\theta^n}` for a number of modes :math:`m=\theta n` which is linear in the number of photons :math:`n`, where: .. math:: \rho_\theta = \frac{(2\theta+1)^{2\theta+1}}{(4\theta)^{\theta}(\theta+1)^{\theta+1}} For example, if we were to work with dual rail path encoding (ignoring for now the number of auxiliary modes required), we would typically work with :math:`\theta=2`, and the average performance is then :math:`\mathrm{n(\frac{5^5}{8^23^3})^n} \approx \mathrm{n1.8^n}`. See also, its code reference: :ref:`Clifford2017Backend` SLOS ^^^^ The Strong Linear Optical Simulation ``SLOS`` algorithm developed by a subset of the present authors is introduced in :cite:t:`heurtel2022`. It unfolds the full computation path in memory, leading to a remarkable time complexity of :math:`\mathrm{nC_n^{n+m-1}}` for computing the full distribution. The current implementation also allows restrictive sets of outputs, with average computing time in :math:`\mathrm{n\rho_\theta^n}` for single output computation. As discussed in :cite:t:`heurtel2022`, Boson Sampling with ``SLOS`` is possible with the time complexity of :cite:t:`clifford2020faster`, though it has not yet been implemented in the current version of Perceval. The tradeoff in this approach is a huge memory usage in :math:`\mathrm{nC^{n+m-1}_n}` that limits usage on personal computers to circuits with :math:`\approx 20` photons and to :math:`\approx 24` photons on super-computers. See also, its code reference: :ref:`SLOSBackend` SLAP ^^^^ The Simulator of LAttice of Polynomials ``SLAP`` algorithm computes all output probability amplitudes at once by iterating over a lattice of intermediary results representation. It is designed to require less memory than ``SLOS`` (:math:`2^n` complex values) at the cost of a slightly higher computation time. The algorithm is introduced in :cite:t:`goubault2025`. This feature is still under development, however, in the future, we expect this backend: * to reach a sampling efficiency of :math:`\mathrm{O}(n2^n+poly(m,n))` * to be faster than SLOS in the regime :math:`m >> n` See also, its code reference: :ref:`SLAPBackend` Naive ^^^^^ This backend implements direct permanent calculation and is therefore suited for single output probability computation with small memory cost. Both Ryser's (:cite:t:`ryser1963combinatorial`) and Glynn's (:cite:t:`glynn2010permanent`) algorithms have been implemented. Extra-care has been taken on the implementation of these algorithms, with usage of different optimisation techniques including native multithreading and SIMD vectorisation primitives. Benchmarking of these algorithms and comparison with the implementation present in the `The Walrus library `_ is provided in following figure: .. figure:: _static/img/performance-permanent.png :width: 800 :align: center Comparison of the average time [#]_ to calculate a permanent of an :math:`n\times n` Haar random matrix. The processor is a 32 core, 3.1GHz Intel Haswell. For *The Walrus*, version 0.19 is used and installed from `pypi `_. The Ryser implementation is run on 4 or 32 threads. The Glynn implementation is run on a single thread. What is interesting to note is that all implementations have convergence to the theoretical performance but the factor between optimised and less optimised implementation still makes a perceptible time difference for the end-user. See also, its code reference: :ref:`NaiveBackend` NaiveApprox ^^^^^^^^^^^ This backend does the same computations as Naive, but uses Gurvits estimate to compute the permanent (see :cite:t:`gurvits2002`). Aside of usual probability() and prob_amplitude() methods, it offers a 99% confidence interval on the probability, or a 99% sure error bound on the amplitude. A better accuracy can be obtained with a higher iteration count. With this approximated backend, you can achieve a few probability estimates for high photon counts. See also, its code reference: :ref:`NaiveApproxBackend` MPS ^^^ Matrix Product State (MPS) is based on a type of tensor network simulation, which gives an approximation of the output states (see :cite:t:`schollwock2011density` and :cite:t:`oh2021classical`). As the Stepper, MPS backend does the computation on each component of the circuits one-by-one, and not on the whole unitary, but has the unique feature of performing approximate state evolution. The states are represented by tensors, which are then updated at each component. These tensors can be seen as a big set of matrices, and the approximation is done by choosing the dimension of these matrices, called the *bond* dimension. See also, its code reference: :ref:`MPSBackend` .. rubric:: Footnotes .. [1] Those backends technically support sampling, but to do so, they need to compute the full output distribution then sample on it, which is totally inefficient. .. [#] Following the methodology presented at https://the-walrus.readthedocs.io/en/latest/gallery/permanent_tutorial.html. ================================================ FILE: docs/source/bibliography.rst ================================================ Bibliography ============ .. bibliography:: :all: ================================================ FILE: docs/source/build_catalog.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval import catalog def get_pretty_string(s: str): out = '' for i, c in enumerate(s): if i == 0: out += c.upper() continue if s[i-1] == ' ': out += c.upper() continue out += c return out def build_catalog_rst(path: str): out = '' for key in catalog.list(): item = catalog[key] out += get_pretty_string(item.name) + '\n' out += '-'*len(item.name) + '\n\n' out += f'Catalog key: ``{item.name}``\n\n' out += item.description + '\n\n' if item.params_doc: out += 'Parameters:\n' for param_name, param_descr in item.params_doc.items(): out += f' * ``{param_name}``: {param_descr}\n' out += '\n' out += '.. code-block::\n\n' out += ' ' + item.str_repr.replace('\n', '\n ')+'\n\n' if item.see_also: out += f'See also: {item.see_also}\n\n' if item.article_ref: out += f'Scientific article reference: {item.article_ref}\n\n' with open(path, 'w', encoding="utf-8") as file: file.write(out) ================================================ FILE: docs/source/conf.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # """ conf.py used by sphinx to build docs The repo is copied to the correct commit of the tag Then this file is interpreted """ import os import sys from datetime import datetime from pathlib import Path sys.path.insert(0, os.path.relpath("../")) from source import build_catalog from perceval import PMetadata REPO_PATH = Path(__file__).parent.parent.parent.resolve() build_directory = os.path.join(REPO_PATH, "docs", "build") if not os.path.exists(build_directory): os.makedirs(build_directory) build_catalog.build_catalog_rst(os.path.join(build_directory, "catalog.rst")) # -- Project information ----------------------------------------------------- project = PMetadata.name() copyright = f"{datetime.now().year}, {PMetadata.author()[0].capitalize() + PMetadata.author()[1:]}" author = PMetadata.author() release = PMetadata.short_version() # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.mathjax", "sphinx_autodoc_typehints", "sphinx.ext.autosectionlabel", "sphinxcontrib.bibtex", "enum_tools.autoenum", "nbsphinx", "sphinx_multiversion", ] suppress_warnings = ['autosectionlabel.*'] bibtex_bibfiles = ["references.bib"] bibtex_default_style = "plain" bibtex_reference_style = "label" # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_rtd_theme" # 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_theme_options = { "navigation_depth": 2, "titles_only": False, } html_style = "css/style.css" html_logo = "_static/img/Perceval logo white 160X160.png" html_favicon = "_static/img/Perceval icon white 32x32.ico" nbsphinx_execute_arguments = [ "--InlineBackend.figure_formats={'svg', 'pdf'}", "--InlineBackend.rc={'figure.dpi': 96}", ] ================================================ FILE: docs/source/contributing.rst ================================================ Contributions ============= Initial Contributors -------------------- The first contributors to the frameworks are (alphabetically ordered): Nadia Belabas [C2N]_, Boris Bourdoncle [QUA]_, Pierre-Emmanuel Emeriau [QUA]_, Andreas Fyrillas [QUA]_, Grégoire de Gliniasty [QUA]_, Nicolas Heurtel [QUA]_, Raphaël Le Bihan [QUA]_, Sébastien Malherbe [ENS]_, Rawad Mezher [QUA]_, Shane Mansfield [QUA]_, Luka Music [QUA]_, Marceau Paillhas [QUA]_, Jean Senellart [QUA]_, Pascale Senellart [QUA]_ [C2N]_, Mario Valdiva [QUA]_, Benoît Valiron [CEN]_ .. [C2N] Centre for Nanosciences and Nanotechnology, CNRS, Universite Paris-Saclay, UMR 9001, 10 Boulevard Thomas Gobert, 91120, Palaiseau, France .. [QUA] Quandela, 7 Rue Léonard de Vinci, 91300 Massy, France .. [ENS] Ecole Normale Supérieure, 45 rue d’Ulm, 75230 Paris, France .. [CEN] Université Paris-Saclay, Inria, CNRS, ENS Paris-Saclay, Centrale Supélec, LMF, 91190, 15 Gif-sur-Yvette, France Welcoming Contributors ---------------------- Perceval has been built and open-source for the benefit of the full photonics quantum community. You can contribute by sharing feedback, questions, ideas, feedback on experiments on our forum at https://perceval.quandela.net/forum. As a community project, you are also welcome to fork Perceval and submitting merge requests. See https://github.com/Quandela/Perceval/blob/main/CONTRIBUTING.md for more information. ================================================ FILE: docs/source/examples_boson_sampling.rst ================================================ Boson Sampling ============== .. toctree:: :maxdepth: 2 notebooks/Boson_sampling notebooks/MPS_techniques_for_boson_sampling ================================================ FILE: docs/source/examples_others.rst ================================================ Others ====== .. toctree:: :maxdepth: 2 notebooks/Gedik_qudit notebooks/Boson_Bunching notebooks/quantum_kernel_methods notebooks/BS-based_implementation notebooks/LOv_rewriting_rules ================================================ FILE: docs/source/examples_quantum_walk.rst ================================================ Quantum Walk ============ .. toctree:: :maxdepth: 2 notebooks/Two-particle_bosonic-fermionic_quantum_walk ================================================ FILE: docs/source/examples_standard_algo.rst ================================================ Standard Quantum Algorithms =========================== .. toctree:: :maxdepth: 2 notebooks/Walkthrough-cnot notebooks/Shor_Implementation notebooks/2-mode_Grover_algorithm ================================================ FILE: docs/source/examples_vqa.rst ================================================ Variational Quantum Algorithms ============================== .. toctree:: :maxdepth: 2 notebooks/Differential_equation_resolution notebooks/Variational_Quantum_Eigensolver notebooks/Reinforcement_learning notebooks/QUBO ================================================ FILE: docs/source/getting_started.rst ================================================ Getting started =============== What is Perceval? ^^^^^^^^^^^^^^^^^ Perceval is a toolbox containing generic functions and classes, built around an optimised native core (see :ref:`exqalibur` code reference). It offers tools to: * Manipulate quantum states in the Fock space * Pure states (:ref:`FockState`, :ref:`StateVector`) * Mixed states (:ref:`SVDistribution`, :ref:`DensityMatrix`) * Build a linear optics :ref:`Experiment` containing * A unitary :ref:`Circuit` composed of :ref:`Unitary Components` * Some :ref:`Non-unitary Components` * Feed-forward through :ref:`Feed-forward Configurators` * Variable :ref:`parameters` and :ref:`expressions` to parametrise components * Display circuits and data (:ref:`pdisplay`), serialise them (:ref:`serialization`) * Define real-world noise parameters applied in the input, the linear-optics circuit and the photon detectors (:ref:`Noise Model`) * Simulate these experiments through :ref:`different layers of simulations` * Perfect simulations with :ref:`Simulation Back-ends` * Noisy and non-unitary simulations with the :ref:`Simulator` layer * Control the flow of quantum computations and choose where they are run: * Locally with the :ref:`Processor`, remotely with the :ref:`RemoteProcessor` * Manage your :ref:`jobs` with the :ref:`JobGroup` Installing Perceval ^^^^^^^^^^^^^^^^^^^ *Perceval* supports several *Python* versions (typically, `those that are not in "end-of-life" `_). In a virtual environment of any *Python* supported version, a single :code:`pip` command installs Perceval and all of its dependencies. .. code-block:: bash $ pip install perceval-quandela .. warning:: Pay attention that the *Python* package name is "perceval-quandela" and not "perceval" Once the above command succeeds, you can start typing code in your favorite IDE! Hello world ^^^^^^^^^^^ The following example is a minimal code to simulate the `Hong–Ou–Mandel effect `_ on the user's computer in a noisy situation, and retrieve both a sample count and exact probabilities computed by a strong simulation back-end. >>> import perceval as pcvl >>> from perceval.algorithm import Sampler >>> >>> input_state = pcvl.BasicState("|1,1>") # Inject one photon on each input mode... >>> circuit = pcvl.BS() # ... of a perfect beam splitter >>> noise_model = pcvl.NoiseModel(transmittance=0.05, indistinguishability=0.85) # Define some noise level >>> >>> processor = pcvl.Processor("SLOS", circuit, noise=noise_model) # Use SLOS, a strong simulation back-end >>> processor.min_detected_photons_filter(1) # Accept all output states containing at least 1 photon >>> processor.with_input(input_state) >>> >>> sampler = Sampler(processor) >>> samples = sampler.sample_count(10_000)['results'] # Ask to generate 10k samples, and get back only the raw results >>> probs = sampler.probs()['results'] # Ask for the exact probabilities >>> print(f"Samples: {samples}") >>> print(f"Probabilities: {probs}") Samples: { |2,0>: 117 |0,2>: 147 |1,0>: 4822 |1,1>: 22 |0,1>: 4892 } Probabilities: { |2,0>: 0.011858974358974369 |0,2>: 0.011858974358974369 |1,1>: 0.0019230769230769245 |1,0>: 0.48717948717948717 |0,1>: 0.48717948717948717 } Now that you can run some code, let's continue with a tutorial to learn Perceval syntax. ================================================ FILE: docs/source/index.rst ================================================ :github_url: https://github.com/Quandela/Perceval .. figure:: _static/img/perceval.jpg :align: right :width: 250 :figwidth: 250 :alt: Extract from Chrétien de Troyes Perceval, the Story of the Grail – Chrétien de Troyes (circa 1180) Welcome to the Perceval documentation! ====================================== Perceval is an open source linear optics quantum framework. It provides a powerful language to describe linear optics setups through a simple object-oriented API, and is able to simulate them and send computation requests to remote Quantum Processing Units (QPU) and simulators. * To start using Perceval, see: :ref:`Getting started` * To contribute to Perceval, see: :ref:`Welcoming Contributors` Perceval has been developed as a complete toolkit for physicists, computer scientists, students, researchers, and practitioners of quantum computing. It can be used to reproduce published experimental works or to experiment directly with a new generation of quantum algorithms. .. |pcvl_paper_link| raw:: html Perceval white paper If you are using Perceval for academic work, please cite the |pcvl_paper_link| as: .. code:: latex @article{heurtel2023perceval, doi = {10.22331/q-2023-02-21-931}, url = {https://doi.org/10.22331/q-2023-02-21-931}, title = {Perceval: {A} {S}oftware {P}latform for {D}iscrete {V}ariable {P}hotonic {Q}uantum {C}omputing}, author = {Heurtel, Nicolas and Fyrillas, Andreas and Gliniasty, Gr{\'{e}}goire de and Le Bihan, Rapha{\"{e}}l and Malherbe, S{\'{e}}bastien and Pailhas, Marceau and Bertasi, Eric and Bourdoncle, Boris and Emeriau, Pierre-Emmanuel and Mezher, Rawad and Music, Luka and Belabas, Nadia and Valiron, Benoît and Senellart, Pascale and Mansfield, Shane and Senellart, Jean}, journal = {{Quantum}}, issn = {2521-327X}, publisher = {{Verein zur F{\"{o}}rderung des Open Access Publizierens in den Quantenwissenschaften}}, volume = {7}, pages = {931}, month = feb, year = {2023} } Related Projects ================ .. |interop_link| raw:: html perceval-interop .. |merlinquantum_link| raw:: html merlinquantum Perceval is used in several higher-level projects (non-exhaustive list): * **perceval-interop**: Interoperability tools for conversion between photonic and gate based Quantum computing. See the project here: |interop_link|. * **MerLin**: A framework to bring quantum computing to AI practitioners, requiring no prior quantum expertise. Learn more here: |merlinquantum_link|. .. toctree:: :maxdepth: 2 :hidden: getting_started tutorial_beginner tutorial_advanced tutorial_expert legacy .. toctree:: :caption: Code Reference :maxdepth: 2 :hidden: reference/algorithm/index reference/backends/index reference/components/index reference/error_mitigation reference/providers reference/rendering/index reference/runtime/index reference/serialization reference/simulators/index reference/utils/index reference/exqalibur/index .. toctree:: :caption: Examples :maxdepth: 2 :hidden: examples_boson_sampling examples_standard_algo examples_vqa examples_quantum_walk examples_others .. toctree:: :caption: Community contributing bibliography ================================================ FILE: docs/source/legacy.rst ================================================ Legacy ====== While, with its latest versions, Perceval tends to stabilise its public API, some changes may break existing user code. This section lists the major breaking changes. Breaking changes in Perceval 1.1 -------------------------------- JobGroup number of parallel launch ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The number of jobs that a user can run is now directly retrieved from the cloud. AS such, the `set_cloud_maximal_job_count` and `get_cloud_maximal_job_count` from `RemoteConfig` are now deprecated and no longer work. Breaking changes in Perceval 1.0 -------------------------------- FockState was split in three different classes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To achieve better optimisation in noisy simulation and to clarify the intent of different states usage, it has been decided to get rid of the former generic :code:`FockState` that could hold richly annotated photons as well as just a plain perfect state. Definition of the new classes ............................. * :code:`FockState`: A light-weight object only containing photon positions in mode (e.g. :code:`|1,0,1>`). Can be used to represent detections. * :code:`NoisyFockState`: A collection of indistinguishable photon groups, that are totally distinguishable. The distinguishability index is an integer and is referred to as the `noise tag` (e.g. :code:`|{0},{1},{0}{2}>` contains three groups of indistinguishable photons tagged 0, 1 and 2). * :code:`AnnotatedFockState`: Replace the previous :code:`FockState` by allowing rich annotations, having one or more string types, each having a complex number for value. This enables to accurately encode physical parameters and play with partial distinguishability (e.g. :code:`|{P:H,lambda:0.625},{P:V,lambda:0.618}>`). Please note that apart from polarisation, `Perceval` does not provide a generic algorithm to separate rich annotated states, and the user would have to write one. The most breaking change here is that perceval is not able to simulate :code:`AnnotatedFockState`, apart from polarized ones. Any code manually using annotations to generate distinguishability must be changed to use the new :code:`NoisyFockState` class. For instance, a :code:`BasicState("|{_:0}, {_:1}>")` from perceval 0.x must be changed to :code:`BasicState("|{0}, {1}>")` to be able to be simulated. For more advanced usage of :code:`AnnotatedFockState` and :code:`NoisyFockState`, see the new :ref:`Quantum States` notebook. Some calls will use or return only the type that makes sense (e.g. :code:`AnnotatedFockState::threshold_detection()` always returns a :code:`FockState` as a detected state naturally loses all kinds of photon annotation. .. note:: Note that arithmetic still works between states of different types. The result is the most complex type of both operands (e.g. :code:`NoisyFockState` ⊗ :code:`FockState` gives a :code:`NoisyFockState`). Usage in Perceval ................. The :code:`BasicState` class still exists and has the same responsibility as before: representing any non superposed pure state. It can construct any of the forementioned state type from a string representation, of vectors of position, and optionally noise tags or annotations. Even though, `Perceval` code makes it so :code:`isinstance(any_fockstate, BasicState)` returns :code:`True`, the type hinting of user code in an IDE could alert that the types do not match after the update. .. note:: :code:`StateVector` (and therefore :code:`SVDistribution`) accepts any of the three Fock state types as components. Processor add with Component or Circuit ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When adding a Circuit or a Component to a Processor on non-consecutive modes, a permutation was added so that we could add the component to the Processor. The inverse permutation is now also added after the component so that the in-between modes are not impacted by the addition, similarly to what was already done when adding a Processor to a Processor. BSDistribution and SVDistribution ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ These classes have been moved to Exqalibur with a C++ implementation. As such, they are no longer Python dictionaries and may not support some advanced dict features. This has several consequences: - You can no longer instantiate :code:`BSDistribution` or :code:`SVDistribution` using a dictionary with mixed type keys, nor with non-BasicState or non-StateVector keys. - :code:`BSDistribution` and :code:`SVDistribution` can no longer be compared to a regular :code:`dict` (for example by using :code:`==`). - The order of insertion is no longer preserved. - :code:`keys()` and :code:`values()` methods now return an iterator, so methods like :code:`len` no longer work on their result. Also, note that: - Inserting a :code:`StateVector` in :code:`SVDistribution` no longer normalises it. - Using the tensor product with an empty distribution now always returns an empty distribution. To keep the same behaviour as before (the result was the non-empty distribution), one would have to replace the empty distribution by a distribution containing a void state (:code:`BSDistribution(BasicState())`) for tensor product or a 0-photon state (:code:`BSDistribution(BasicState(m))`) for a merge. StateVector ^^^^^^^^^^^ The method :code:`StateVector.keys()` now returns an iterator on the keys instead of a BSSamples. This avoids doing unnecessary copy. Please note that due to this change: - Keys must now be copied before being modified when iterating on :code:`StateVector.keys()`. - :code:`StateVector.keys()` no longer has list methods such as :code:`len`, :code:`__getitem__`... Removal of deprecated methods and classes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The following methods and classes have been removed or definitely modified as they were deprecated: - :code:`TokenProvider` (deprecated since 0.13, replaced by :code:`RemoteConfig`) - :code:`AProbAmpliBackend` (deprecated since 0.12, replaced by :code:`AStrongSimulationBackend`) - :code:`postselect_independent` (deprecated since 0.12, replaced by :code:`PostSelect` method :code:`is_independent_with`) - The :code:`n` parameter of SLOS backend (deprecated since 0.12, now automatically chosen when using :code:`set_input_state`) - :code:`thresholded_output` method of :code:`Processor` and :code:`RemoteProcessor` (deprecated since 0.12, replaced by adding several :code:`Detector.threshold()`) - :code:`with_polarized_input` method of :code:`Processor` (because :code:`Processor.with_input` is now able to handle a polarized :code:`AnnotatedFockState` transparently) - :code:`tensorproduct(states: list)` from :code:`perceval.utils` (due to tensor products being handled well by multiplication operators and specific methods - see :code:`BSDistribution.list_tensor_product`, for instance) - :code:`JobGroup.list_existing()` has been renamed into :code:`JobGroup.list_locally_saved()` NoiseModel ^^^^^^^^^^ The way of :code:`NoiseModel` to handle its attributes has changed to be more pythonic. Now, your IDE should be able to tell that the attributes exist in the class, and the attributes can be changed using a syntax like :code:`noise_model.g2 = 0.1`. This change is accompanied by the removal of some methods: - The :code:`__getitem__` has been removed since it was giving a class that is not accessible anymore - The :code:`set_value` method has been removed, and can be replaced either by spelling directly the attribute (:code:`noise_model.g2 = 0.1`) or by using the python method :code:`setattr(noise_model, "g2", 0.1)`. Older changes ------------- The documentation to update from an older legacy version to a more recent one can still be found `here `_. ================================================ FILE: docs/source/notebooks/2-mode_Grover_algorithm.ipynb ================================================ { "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "3a271506", "metadata": {}, "source": [ "# 2-mode Grover's search algorithm" ] }, { "attachments": {}, "cell_type": "markdown", "id": "34fa32af", "metadata": {}, "source": [ "We implement in this notebook a 2-mode optical realization of Grover's search algorithm, based on Kwiat et al. (2000). Grover’s search algorithm: An optical approach. [Journal of Modern Optics](https://doi.org/10.1080/09500340008244040), 47(2–3), 257–266." ] }, { "attachments": {}, "cell_type": "markdown", "id": "e22c5dde", "metadata": {}, "source": [ "## Introduction" ] }, { "attachments": {}, "cell_type": "markdown", "id": "c04c528f", "metadata": {}, "source": [ "### Motivation\n", "\n", "Searching for a specific item (called the marked item) in an unstructured list of $N$ items requires $O(N)$ accesses to the list classically. Grover showed in 1996 that is possible for a quantum computer to achieve this using only $O\\left(\\sqrt{N}\\right)$ iterations." ] }, { "attachments": {}, "cell_type": "markdown", "id": "08145e4a", "metadata": {}, "source": [ "### Algorithm summary\n", "\n", "For a list of size $N$, Grover's algorithm requires $\\log (N)$ qubits. The algorithm starts by setting each qubit in the superposition state $\\frac{1}{\\sqrt{2}}\\left(|0\\rangle+|1\\rangle\\right)$. Then it applies $O\\left(\\sqrt{N}\\right)$ iterations of a subroutine called inversion-about-mean, whose goal is to skew this initial uniform superposition state towards the desired marked state such the probability of measuring the marked state is amplified. This subroutine requires the application of an oracle unitary, which applies a relative $\\pi$ phase shift only to the quantum state encoding the item we are looking for in the database." ] }, { "attachments": {}, "cell_type": "markdown", "id": "5690fa9d", "metadata": {}, "source": [ "### Kwiat et al. implementation details\n", "\n", "The optical implementation of Kwiat et al. uses the polarization and path degree of freedom of a single beam to achieve a 2-qubit optical implementation of Grover's search algorithm. Although $N=4$ here, calculations show that only a single application of the inversion-about-mean subroutine is required.\n", "\n", "In an effort to reduce the number of optical components used in the experimental setup, the authors work with a compiled version of the circuit, which we will reproduce here using Perceval." ] }, { "attachments": {}, "cell_type": "markdown", "id": "7bc2f8dc", "metadata": {}, "source": [ "## Perceval implementation" ] }, { "attachments": {}, "cell_type": "markdown", "id": "904f535e", "metadata": {}, "source": [ "### Initialisation" ] }, { "cell_type": "code", "execution_count": 1, "id": "4d4c8ff1", "metadata": {}, "outputs": [], "source": [ "import math\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "import perceval as pcvl\n", "from perceval.algorithm import Analyzer" ] }, { "attachments": {}, "cell_type": "markdown", "id": "09fa856a", "metadata": {}, "source": [ "We create in Perceval a circuit with two spatial modes, $a$ and $b$ denoting resapectively the lower and upper spatial modes. For clarity, the different equivalent encodings for each of the four basis states are given below in order:\n", "- marked item encoding: $\\left|\"00\"\\right\\rangle$, $\\left|\"01\"\\right\\rangle$, $\\left|\"10\"\\right\\rangle$, $\\left|\"11\"\\right\\rangle$\n", "- Kwiat et al. path and polarization encoding: $\\left|aH\\right\\rangle$, $\\left|aV\\right\\rangle$, $\\left|bH\\right\\rangle$, $\\left|bV\\right\\rangle$\n", "- Perceval path and polarization encoding: $\\left|0, 1:H\\right\\rangle$, $\\left|0, 1:V\\right\\rangle$, $\\left|1:H, 0\\right\\rangle$, $\\left|1:V, 0\\right\\rangle$\n", "\n", "We first define these states and their mode equivalent in Perceval:" ] }, { "cell_type": "code", "execution_count": 2, "id": "b8084f08", "metadata": {}, "outputs": [], "source": [ "states = [pcvl.BasicState(\"|0,{P:H}>\"),\n", " pcvl.BasicState(\"|0,{P:V}>\"),\n", " pcvl.BasicState(\"|{P:H},0>\"),\n", " pcvl.BasicState(\"|{P:V},0>\"),\n", " ]\n", "\n", "states_modes = [\n", " pcvl.BasicState([0, 0, 0, 1]),\n", " pcvl.BasicState([0, 0, 1, 0]),\n", " pcvl.BasicState([0, 1, 0, 0]),\n", " pcvl.BasicState([1, 0, 0, 0])\n", "]" ] }, { "attachments": {}, "cell_type": "markdown", "id": "fab191db", "metadata": {}, "source": [ "We use the following unitary matrix to represent the beamsplitters:" ] }, { "cell_type": "code", "execution_count": 3, "id": "a3560b8f", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\frac{\\sqrt{2}}{2} & - \\frac{\\sqrt{2}}{2}\\\\\\frac{\\sqrt{2}}{2} & \\frac{\\sqrt{2}}{2}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "bsry = pcvl.BS.Ry()\n", "pcvl.pdisplay(bsry.U)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "54638f8d", "metadata": {}, "source": [ "The half-wave plates are defined in the article as:" ] }, { "cell_type": "code", "execution_count": 4, "id": "54d17968", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "ξ=pi/2\n", "δ=pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "0\n", "0\n", "" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def HWP(xsi):\n", " hwp = pcvl.Circuit(m=1)\n", " hwp.add(0, pcvl.HWP(xsi)).add(0, pcvl.PS(-math.pi/2))\n", " return hwp\n", "\n", "pcvl.pdisplay(HWP(math.pi/2))" ] }, { "attachments": {}, "cell_type": "markdown", "id": "3d3c98ab", "metadata": {}, "source": [ "### Circuit Construction" ] }, { "attachments": {}, "cell_type": "markdown", "id": "b2c267cb", "metadata": {}, "source": [ "We divide the compiled circuit of Kwiat et al. in three parts: [state initialization](#state-initialization-circuit), [oracle](#oracle) and [inversion about mean](#inversion-about-mean). However, due to the compilation, each individual part does not act exactly as described in the introduction." ] }, { "attachments": {}, "cell_type": "markdown", "id": "14ee9683", "metadata": {}, "source": [ "\n", "#### State initialization circuit" ] }, { "cell_type": "code", "execution_count": 5, "id": "f6718866", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "ξ=pi/8\n", "δ=pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Ry\n", "\n", "\n", "Φ=pi\n", "\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "init_circuit = (pcvl.Circuit(m=2, name=\"Initialization\")\n", " // HWP(math.pi / 8)\n", " // bsry\n", " // pcvl.PS(-math.pi))\n", "\n", "pcvl.pdisplay(init_circuit)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "e8278f70", "metadata": {}, "source": [ "#### Oracle\n", "\n", "The oracle circuit can be initialised so that any one of the four list elements are marked. This is controlled via the integer parameter $mark \\in [0, 3]$." ] }, { "cell_type": "code", "execution_count": 6, "id": "96aba62a", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "ξ=0\n", "δ=pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "\n", "\n", "δ=pi/2\n", "\n", "\n", "\n", "ξ=0\n", "δ=pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def oracle(mark: int):\n", " \"\"\"Values 0, 1, 2 and 3 for parameter 'mark' respectively mark the elements \"00\", \"01\", \"10\" and \"11\" of the list.\"\"\"\n", " oracle_circuit = pcvl.Circuit(m=2, name='Oracle')\n", " # The following dictionary translates n into the corresponding component settings\n", " oracle_dict = {0: (1, 0), 1: (0, 1), 2: (1, 1), 3: (0, 0)}\n", " PC_state, LC_state = oracle_dict[mark]\n", " # Mode b\n", " if PC_state == 1:\n", " oracle_circuit //= HWP(0)\n", " oracle_circuit.add(0, pcvl.PR(math.pi/2))\n", " if LC_state == 1:\n", " oracle_circuit //= HWP(0)\n", " # Mode a\n", " if LC_state == 1:\n", " oracle_circuit //= (1, HWP(0))\n", " if PC_state == 1:\n", " oracle_circuit //= (1, HWP(0))\n", " return oracle_circuit\n", "\n", "pcvl.pdisplay(oracle(0))" ] }, { "attachments": {}, "cell_type": "markdown", "id": "50596a16", "metadata": {}, "source": [ "#### Inversion about mean" ] }, { "cell_type": "code", "execution_count": 7, "id": "ef839994", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Ry\n", "\n", "\n", "\n", "ξ=pi/4\n", "δ=pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Ry\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "inversion_circuit = (pcvl.Circuit(m=2, name='Inversion')\n", " // bsry\n", " // HWP(math.pi / 4)\n", " // bsry)\n", "\n", "pcvl.pdisplay(inversion_circuit)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "4daf70f2", "metadata": {}, "source": [ "#### Detection\n", "\n", "The article also uses a detection circuit of the form:" ] }, { "cell_type": "code", "execution_count": 8, "id": "31ad50c7", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "detection_circuit = pcvl.Circuit(m=4, name='Detection')\n", "detection_circuit.add((0, 1), pcvl.PBS())\n", "detection_circuit.add((2, 3), pcvl.PBS())\n", "\n", "pcvl.pdisplay(detection_circuit)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "76acc62f", "metadata": {}, "source": [ "However, Perceval allows us to filter out the photon's polarization state, meaning that there is no need to expand the circuit to four output spatial modes.\n", "\n", "For now, we will need this particular circuit." ] }, { "attachments": {}, "cell_type": "markdown", "id": "2326568e", "metadata": {}, "source": [ "#### Final circuit setup \n", "\n", "As above, the value of parameter 'mark' indicates which element of the list needs to be found." ] }, { "cell_type": "code", "execution_count": 9, "id": "1b0bffde", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Grover optical circuit for searching database element \"00\":\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "INITIALIZATION\n", "\n", "\n", "\n", "ξ=pi/8\n", "δ=pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Ry\n", "\n", "\n", "Φ=pi\n", "\n", "\n", "ORACLE\n", "\n", "\n", "\n", "ξ=0\n", "δ=pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "\n", "\n", "δ=pi/2\n", "\n", "\n", "\n", "ξ=0\n", "δ=pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "INVERSION\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Ry\n", "\n", "\n", "\n", "ξ=pi/4\n", "δ=pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Ry\n", "\n", "\n", "\n", "\n", "\n", "\n", "DETECTION\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def grover_circuit(mark: int):\n", " grover = pcvl.Circuit(m=4, name='Grover')\n", " grover.add(0, init_circuit).add(0, oracle(mark)).add(0, inversion_circuit)\n", " grover.add(1, pcvl.PERM([1, 0])).add(0, detection_circuit)\n", " return grover\n", "\n", "print('Grover optical circuit for searching database element \"00\":')\n", "pcvl.pdisplay(grover_circuit(0), recursive=True)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "922a8f12", "metadata": {}, "source": [ "## Grover algorithm execution\n", "\n", "We can finally simulate Grover's algorithm for marked database elements \"00\", \"01\", \"10\" and \"11\"." ] }, { "cell_type": "code", "execution_count": 10, "id": "0f000dac", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Circuit simulation\n", "input_state = pcvl.BasicState(\"|{P:H},0, 0, 0>\")\n", "results_list = [] # probability amplitudes storage\n", "\n", "for mark in range(4):\n", " p = pcvl.Processor(\"SLOS\", grover_circuit(mark))\n", " a = Analyzer(p, input_states=[input_state], output_states=states_modes)\n", " results_list.append(a.distribution[0])\n", "\n", "# Plot data\n", "labels = ['\"00\"', '\"01\"', '\"10\"', '\"11\"']\n", "x = np.arange(4) # label locations\n", " \n", "fig, ax = plt.subplots(dpi=150)\n", "for result, state in zip(results_list, states):\n", " ax.bar(x, result.real, 0.1, label=str(state))\n", "\n", "ax.set_xlabel('Marked database element')\n", "ax.set_ylabel('Detection probability') \n", "ax.set_xticks(x, labels)\n", "ax.legend()\n", "ax.grid(True, axis='x')\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "659482dd", "metadata": {}, "source": [ "As demonstrated by the graph above, Grover's algorithm indeed finds the marked database element!" ] }, { "attachments": {}, "cell_type": "markdown", "id": "d32d028f", "metadata": {}, "source": [ "## Reference\n", "\n", "> Kwiat et al. Grover’s search algorithm: An optical approach. [Journal of Modern Optics](https://doi.org/10.1080/09500340008244040), 47(2–3), 257–266 (2000).\n" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Advanced_state_tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "6c48eed1a03dc991", "metadata": {}, "source": [ "# Representing partially dinstinguishable states" ] }, { "cell_type": "markdown", "id": "8dffc439586728bb", "metadata": {}, "source": [ "In this notebook, we use the different kinds of `BasicState` to present how the user can handle complex inputs using perceval." ] }, { "cell_type": "code", "execution_count": 1, "id": "e372a8863b8965ff", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "from perceval import BasicState, AnnotatedFockState, StateVector, Annotation" ] }, { "cell_type": "markdown", "id": "5df9f6df6986294d", "metadata": {}, "source": [ "## BasicState insight" ] }, { "cell_type": "markdown", "id": "84c4ba30e0915672", "metadata": {}, "source": [ "A `BasicState` is a class that embodies three different kinds of Fock states. The class init chooses the correct representation according to its arguments, and most methods are common to all the classes.\n", "\n", "- The `FockState` represents indistinguishable photons. The representation shows the number of photon in each mode (e.g. $|0, 1, 0, 2\\rangle$)\n", "- The `NoisyFockState` represents groups of indistinguishable photons, where each group is represented using an integer (the 'noise tag') from 0 to 255. The photons from different groups are totally distinguishable and do not interact at all. The representation shows the noise tag between brackets (e.g. $|0, \\{0\\}, 0, \\{0\\}\\{1\\}\\rangle$)\n", "- The `AnnotatedFockState` is a generic state class where photons can be given string tags associated with values. The representation shows the tag and the value separated by a semicolon, all between brackets (e.g. $|0, \\{P:0\\}, 0, \\{P:1.57\\}\\{P:3.14\\}\\rangle$).\n", "\n", "In the general case, it is not possible to simulate the results of an `AnnotatedFockState`.\n", "The user can provide a way to compute distinguishability from annotations comparison, which allows to handle some physical properties. Here we show an example of computations with photon wavelength, by converting an `AnnotatedFockState` into a superposition of `FockState` and `NoisyFockState`." ] }, { "cell_type": "markdown", "id": "d6a007889e9cd8b", "metadata": {}, "source": [ "## Example: wavelength\n", "\n", "Suppose you have two photons of different wavelength arriving at a perfect beam splitter. We want to study how a difference in wavelength will affect the HOM effect." ] }, { "cell_type": "code", "execution_count": 2, "id": "initial_id", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "|{wavelength:6.25e-07},{wavelength:6.15e-07}>\n" ] } ], "source": [ "# This creates two AnnotatedFockStates with an associated wavelength\n", "state_1 = BasicState(\"|{wavelength:625e-9}>\") # The first source produced state\n", "state_2 = BasicState(\"|{wavelength:615e-9}>\") # The second source produced state\n", "\n", "# The whole input state arriving at the chip is then\n", "input_state = state_1 * state_2\n", "print(input_state)" ] }, { "cell_type": "markdown", "id": "42647cee52df73e", "metadata": {}, "source": [ "The problem now is that perceval doesn't know how distinguishable are these photons. So, as a user, we first need to convert this to something that Perceval can simulate.\n", "\n", "For that, we introduce a model where the indistinguishability depends on the wavelength of the two photons." ] }, { "cell_type": "code", "execution_count": 3, "id": "6e3a20cceee955ff", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.999*|1,1>+0.032*|{0},{1}>\n" ] } ], "source": [ "def compute_indistinguishability(photon_1: Annotation, photon_2: Annotation):\n", " # To make sense, this method must at least verify:\n", " # - compute_indistinguishability(a, b) == compute_indistinguishability(b, a)\n", " # - compute_indistinguishability(a, a) == 1\n", " lambda_1 = photon_1[\"wavelength\"]\n", " lambda_2 = photon_2[\"wavelength\"]\n", " return np.exp(- 2 * (lambda_2 - lambda_1) ** 2 / (lambda_1 * lambda_2))\n", "\n", "def convert_state(state: AnnotatedFockState) -> StateVector:\n", " assert state.n == 2 # This method would fail otherwise\n", "\n", " photon1 = state.get_photon_annotation(0) # Get the annotation from the first photon\n", " photon2 = state.get_photon_annotation(1) # Get the annotation from the second photon\n", "\n", " indist = compute_indistinguishability(photon1, photon2)\n", "\n", " indist_state = state.clear_annotations() # This creates a FockState with photons at the same place\n", " # Same result than BasicState(list(state))\n", "\n", " noise = (1 - indist ** 2) ** 0.5 # So the final result is normalized\n", "\n", " noisy_state = BasicState(list(state), [0, 1]) # Creates a NoisyFockState with tags 0 and 1 so the two photons are distinguishable\n", "\n", " return indist_state * indist + noisy_state * noise # Creates a StateVector containing only state types that Perceval can simulate\n", "\n", "converted_input_state = convert_state(input_state)\n", "\n", "print(converted_input_state)" ] }, { "cell_type": "markdown", "id": "b802cc403ebce48d", "metadata": {}, "source": [ "We can now simulate using our state as usual." ] }, { "cell_type": "code", "execution_count": 4, "id": "d2fd16c1032d74e1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", "\t|0,2>: 0.4997399722916615\n", "\t|2,0>: 0.4997399722916615\n", "\t|1,1>: 0.0005200554166770454\n", "}\n" ] } ], "source": [ "from perceval import BS, Processor\n", "\n", "p = Processor(\"SLAP\", BS())\n", "p.min_detected_photons_filter(0)\n", "p.with_input(converted_input_state)\n", "\n", "print(p.probs()[\"results\"])" ] }, { "cell_type": "markdown", "id": "bf339ce618959ab5", "metadata": {}, "source": [ "Great! You now know how to create and use states defining (partially) distinguishable photons.\n", "Knowing how to use `BasicStates` that are more than just photon positions is a valuable tool for bigger and tougher problems, especially when state arithmetics is involved.\n", "\n", "The `AnnotatedFockState` can also be used to define polarization using the tag \"P\", that Perceval can natively handle, and that requires a more complex conversion (duplication of the circuit and state size, conversion back at the end...)" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/BS-based_implementation.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "3d4fc10a", "metadata": {}, "source": [ "# Error-tolerant BS-based circuit" ] }, { "cell_type": "markdown", "id": "8cdc5601", "metadata": {}, "source": [ "In this notebook, we aim at presenting an alternative form of \"generic\" interferometers using a BS-based structure as a building block rather than a MZI (Mach-Zehnder interferometer) as a building block. BS-based circuits seem to be more tolerant to manufacturing errors and losses according to Fldzhyan et al. *Optimal design of error-tolerant reprogrammable multiport interferometers*, [Optics Letters](https://doi.org/10.1364/OL.385433), 45(9):2632–2635 (2020)" ] }, { "cell_type": "markdown", "id": "561b1415", "metadata": {}, "source": [ "## Introduction " ] }, { "cell_type": "markdown", "id": "94227072", "metadata": {}, "source": [ "### Parameters" ] }, { "cell_type": "markdown", "id": "897a99d7", "metadata": {}, "source": [ "The goal is to successfully implement a random unitary $U_{target}$ by varying parameters of a fix \"generic\" interferometer $U_{interf}$. For instance, the Reck decomposition [2] has MZI as building block and by varying the angle of variable phase shifts, any unitary can be implemented. However, as we will see, this is only true when the MZI building block have perfect 50:50 beam-splitters which is not realistic in practice due to manufacturing errors. To see this, we will introduce a parameter $\\alpha$ which characterise the error caused by the imbalance of the static BS due to imperfect realisation. And we will investigate how this impacts the correct implementation of any unitary (drawn from the Haar measure) by minimising the infidelity (or maximising the fidelity) between the target unitary $U_{target}$ and its implementation $U_{interf}$.\n", "We will compare this with the BS-based approach [1]." ] }, { "cell_type": "markdown", "id": "5f57bc2b", "metadata": {}, "source": [ "#### Initialisation" ] }, { "cell_type": "code", "execution_count": 1, "id": "190e653d", "metadata": {}, "outputs": [], "source": [ "import datetime\n", "import math\n", "import os\n", "import random\n", "import time\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from scipy.optimize import basinhopping\n", "from tqdm.notebook import tqdm_notebook\n", "\n", "import perceval as pcvl" ] }, { "cell_type": "markdown", "id": "bcd40037", "metadata": {}, "source": [ "We start by defining all parameters used in the program later on:" ] }, { "cell_type": "code", "execution_count": 2, "id": "09dfc317", "metadata": {}, "outputs": [], "source": [ "N=5\n", "n_try=10\n", "n_iter=3\n", "n_process=8\n", "angle_min=-15\n", "angle_max=30\n", "angle_step=2\n", "n_unitary=300\n", "logfilebs='bsbasednotebook-opt'\n", "logfilemzi='mzibasednotebook-opt'" ] }, { "cell_type": "markdown", "id": "98d2cc35", "metadata": {}, "source": [ "## 1. Perceval implementation of the BS-based interferometer" ] }, { "cell_type": "markdown", "id": "e6c9c678", "metadata": {}, "source": [ "We start by describing the circuit as defined in [1] using `pcvl.GenericInterferometer()`. The circuit is built with an arrangement of single static beam-splitters with a variable phase shifter on the same leg. It also starts with phase-shifters on each mode at the beginning." ] }, { "cell_type": "code", "execution_count": 3, "id": "0081f2c8", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=2.63925\n", "\n", "\n", "Φ=0.199141\n", "\n", "\n", "Φ=2.636975\n", "\n", "\n", "Φ=0.975159\n", "\n", "\n", "Φ=3.854011\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_0\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_1\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.296838\n", "\n", "\n", "Φ=3.795011\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_2\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_3\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.452971\n", "\n", "\n", "Φ=4.055302\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_5\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.10296\n", "\n", "\n", "Φ=3.299114\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_6\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_7\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.296254\n", "\n", "\n", "Φ=3.311366\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_8\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_9\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.499205\n", "\n", "\n", "Φ=0.881463\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_10\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_11\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.074899\n", "\n", "\n", "Φ=3.017027\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_12\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_13\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.194637\n", "\n", "\n", "Φ=3.05649\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_14\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_15\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.676151\n", "\n", "\n", "Φ=2.736313\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_16\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_17\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.068325\n", "\n", "\n", "Φ=4.707679\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_18\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta_19\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.923339\n", "\n", "\n", "Φ=4.568611\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "0\n", "1\n", "2\n", "3\n", "4\n", "" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bs = pcvl.GenericInterferometer(N,\n", " lambda idx: pcvl.BS(theta=pcvl.P(\"theta_%d\" % idx)) // (0, pcvl.PS(phi=np.pi * 2 * random.random())),\n", " shape=pcvl.InterferometerShape.RECTANGLE,\n", " depth=2 * N,\n", " phase_shifter_fun_gen=lambda idx: pcvl.PS(phi=np.pi*2*random.random()))\n", "pcvl.pdisplay(bs, recursive = True)" ] }, { "cell_type": "markdown", "id": "6059f2d3", "metadata": {}, "source": [ "### Minimizing infidelity" ] }, { "cell_type": "markdown", "id": "8bb792d0", "metadata": {}, "source": [ "We first define a \"loss\" function which computes the infidelity between the target unitary and the implemented unitary. The fidelity evaluates the performance of the multiport interferometers via $F=\\frac{\\lvert \\mathrm{Tr} ({U_{target}}^{\\dagger} U_{interf}) \\rvert²}{N\\mathrm{Tr}({U_{target}}^{\\dagger} U_{target})}$ where $U_{target}$ is the target matrix and $U_{interf}$ the actual unitary matrix realized by the interferometer and where N is the size of the matrices." ] }, { "cell_type": "code", "execution_count": 4, "id": "2faecde6", "metadata": {}, "outputs": [], "source": [ "def infidelity(c, U_target, params, params_value):\n", " \"\"\"\n", " inputs: \n", " *c* type: circuit from pcvl.Circuit()\n", " role: generic interferometer to optimize \n", " *U_target* type: matrix from pcvl.Matrix.random_unitary()\n", " role: unitary matrix randomly chosen\n", " *params* type: parameters from c.get_parameters()\n", " role: get the parameters associated to the circuit c \n", " *params_value* type: int\n", " role: value of the parameters\n", "\n", " outputs: \n", " *infidelity value* type: int\n", " role: infidelity value between U and U0\n", " \"\"\"\n", " for idx, p in enumerate(params_value):\n", " params[idx].set_value(p) #give a value to each params\n", " U = c.compute_unitary(use_symbolic=False) #\n", " U_dag = np.transpose(np.conjugate(U))\n", " f = abs(np.trace(U_dag @ U_target)) ** 2 / (c.m * np.trace(U_dag @ U))\n", " return 1 - abs(f)" ] }, { "cell_type": "markdown", "id": "81adaa92", "metadata": {}, "source": [ "### Basinhopping algorithm" ] }, { "cell_type": "markdown", "id": "fa4c270f", "metadata": {}, "source": [ "Then, we want to optimise over the angles of the phase-shifters $\\phi$ comprising $U_{interf}$ by minimizing the infidelity $1 - F$ [1]. To do so, we use a numerical optimisation algorithm based on the basinhopping algorithm to explore the space of phases and minimize $1-F$ for a specific value of manufacturing error $\\alpha$. We then vary $\\alpha$ to see how robust to manufacturing errors our scheme is." ] }, { "cell_type": "code", "execution_count": 5, "id": "49aecaa7", "metadata": {}, "outputs": [], "source": [ "def calculate_angle(index_unitary, U_target, alpha):\n", " \"\"\"\n", " inputs:\n", " *index_unitary* type: int\n", " role: index on the number of unitary matrices \n", " *U_target* type: matrix from pcvl.Matrix.random_unitary()\n", " role: unitary matrix randomly chosen\n", " *alpha* type: int\n", " role: angle error due to manufacturing \n", "\n", " outputs:\n", " *infidelity min* type: int\n", " role: min value of infidelity\n", " \"\"\"\n", " c = pcvl.GenericInterferometer(N,\n", " lambda idx: (pcvl.BS(theta=(45 + alpha) / 180 * np.pi)\n", " // (0, pcvl.PS(phi=pcvl.P(\"phi_m%d\" % idx)))),\n", " depth=2 * N,\n", " phase_shifter_fun_gen=lambda idx: pcvl.PS(phi=pcvl.P(\"phi_r%d\" % idx)))\n", " params = c.get_parameters() # We get the parameters of the circuit we have just created\n", " \n", " infidelities = []\n", "\n", " for _ in range(n_try):\n", " init_params = np.random.randn(len(params)) \n", " res = basinhopping(lambda x: infidelity(c, U_target, params, x), init_params, stepsize=0.1, niter=n_iter) # the algorithm is looking for a global minimum of infidelity\n", " infidelities.append(res.fun) # fun is the value of the function at the solution\n", " return min(infidelities)" ] }, { "cell_type": "markdown", "id": "a2b378aa", "metadata": {}, "source": [ "Based on the definition of the loss function, we now explore the space of phases to obtain a correct implementation of the target unitary with our BS-based scheme. We then reiterate the following function for various error parameter $\\alpha$." ] }, { "cell_type": "code", "execution_count": null, "id": "f12a5609", "metadata": {}, "outputs": [], "source": [ "n_angles = (abs(angle_min)+abs(angle_max))/angle_step\n", "\n", "def discovery_unitary(index_unitary):\n", " \"\"\"\n", " inputs:\n", " *param index_unitary* type: int\n", " role: index on the number of unitary matrices\n", " outputs:\n", " type: list of int\n", " role: infidelities for all alpha angles\n", " \"\"\"\n", " np.random.seed(int(10000*time.time()+os.getpid()) & 0xffffffff) #not necessary here, but when we run our program on several cores, this line allows us to generate different unit matrices at the same time.\n", " U_target = pcvl.Matrix.random_unitary(N) # generates the unit random matrix\n", " l_infidelities = []\n", " M = angle_min + math.ceil((angle_max-angle_min)/angle_step)*angle_step + 1\n", "\n", " for alpha in tqdm_notebook(range(angle_min,M,angle_step), desc = \"Angle progress bar unitary {}\".format(index_unitary+1), leave=False):\n", " l_infidelities.append(calculate_angle(index_unitary, U_target, alpha)) \n", " return l_infidelities\n", " \n", "try:\n", " os.remove(\"%s-%d.log\" % (logfilebs,N)) #To avoid having data on our file that we don't want, we delete the data from previous runs\n", "except OSError:\n", " pass\n", "\n", "for index_unitary in tqdm_notebook(range(n_unitary), desc = \"Unitary progress bar\"):\n", " result = discovery_unitary(index_unitary)\n", " e = datetime.datetime.now()\n", " with open(\"%s-%d.log\" % (logfilebs,N), \"a\") as f: #we create our file\n", " f.write(e.strftime(\"%Y-%m-%d %H:%M:%S\")+\"\\t\") #we add the date and the hour of calculation \n", " f.write(\"\\t\".join([\"%g\"%inf for inf in result])) #we add the list of infidelities returned by discovery_unitary()\n", " f.write(\"\\n\")" ] }, { "cell_type": "markdown", "id": "c42f7fe3", "metadata": {}, "source": [ "## Result of the BS-based scheme" ] }, { "cell_type": "markdown", "id": "a3f506da", "metadata": {}, "source": [ "In this section, we treat the previously collected infidelities as a function of $\\alpha$." ] }, { "cell_type": "markdown", "id": "9c4f8e3f", "metadata": {}, "source": [ "The collected data looks like that the output of the following cell. Each lign starts with the date and hour of collection and then, we have values for the minimal infidelity with several values of $\\alpha$. We have a lign per unitary matrix $U_{target}$." ] }, { "cell_type": "code", "execution_count": null, "id": "1fd3af77", "metadata": {}, "outputs": [], "source": [ "with open(\"bsbasednotebook-opt-5.log\", \"r\") as filin:\n", " for line in filin:\n", " print(line)" ] }, { "cell_type": "markdown", "id": "7a16480e", "metadata": {}, "source": [ "### Graph of the infidelity as a function of $\\alpha$ in degree : BS-based" ] }, { "cell_type": "markdown", "id": "25232b73", "metadata": {}, "source": [ "We plot the infidelity as a function of $\\alpha$. To do this, we compute the average of the infidelities over all $U_{target}$ tried." ] }, { "cell_type": "code", "execution_count": null, "id": "b8168095", "metadata": {}, "outputs": [], "source": [ "L=[]\n", "with open(\"bsbasednotebook-opt-5.log\", \"r\") as flog:\n", " for l in flog:\n", " L.append([float (f) for f in l.strip().split(\"\\t\")[1:]])\n", "\n", "A=np.asarray(L)\n", "print(A)\n", "print(A.shape)" ] }, { "cell_type": "code", "execution_count": null, "id": "4ce9d2d6", "metadata": {}, "outputs": [], "source": [ "Lmin=[]\n", "Lmax=[]\n", "Laverage=[]\n", "\n", "for i in range(A.shape[1]):\n", " Langle = A[:,i]\n", " Laverage.append(np.average(Langle))\n", " Langle.sort()\n", " Lmin.append(np.average(Langle[0:10]))\n", " Lmax.append(np.average(Langle[-10:]))\n", "\n", "print(Laverage)\n", "print(Lmin)\n", "print(Lmax)" ] }, { "cell_type": "code", "execution_count": null, "id": "6d77b08a", "metadata": {}, "outputs": [], "source": [ "#data\n", "x = np.linspace(-15, 35, len(Laverage))\n", "\n", "fig, ax = plt.subplots()\n", "\n", "ax.fill_between(x, Lmin, Lmax,alpha=.5,linewidth=0)\n", "ax.plot(x,Laverage,linewidth=2)\n", "\n", "ax.set(xlim=(-15, 35),ylim=(min(Lmin), max(Lmax)))\n", "ax.set_yscale('log')\n", "\n", "\n", "plt.plot(x,Laverage,'b')\n", "plt.title('BS-based', fontsize=20)\n", "plt.xlabel('alpha in degree', fontsize=15)\n", "plt.ylabel('infidelity 1-F', fontsize=15)" ] }, { "cell_type": "markdown", "id": "e35027ee", "metadata": {}, "source": [ "## 2. Perceval implementation of the MZI-based interferometer\n" ] }, { "cell_type": "markdown", "id": "d3586f39", "metadata": {}, "source": [ "Now we do the same construction for the MZI-based circuit. The main difference between both circuits is the structure of the building blocks. Note that the MZI-based circuit has $m$ building blocks with four components whereas the BS-based circuit has $2*m$ building blocks with two components." ] }, { "cell_type": "code", "execution_count": 6, "id": "1d6abe25", "metadata": {}, "outputs": [], "source": [ "def mzi(P1, P2):\n", " mz= (pcvl.Circuit(2, name=\"mzi\")\n", " .add((0, 1), pcvl.BS(theta=(45)/180*np.pi))\n", " .add(0, pcvl.PS(P1))\n", " .add((0, 1), pcvl.BS(theta=(45)/180*np.pi))\n", " .add(0, pcvl.PS(P2)))\n", " return mz" ] }, { "cell_type": "code", "execution_count": 7, "id": "e7fd4aa8", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=phi_r0\n", "\n", "\n", "Φ=phi_r1\n", "\n", "\n", "Φ=phi_r2\n", "\n", "\n", "Φ=phi_r3\n", "\n", "\n", "Φ=phi_r4\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_m0\n", "\n", "\n", "Φ=phi_m1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_n0\n", "\n", "\n", "Φ=phi_n1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_m2\n", "\n", "\n", "Φ=phi_m3\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_n2\n", "\n", "\n", "Φ=phi_n3\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_m4\n", "\n", "\n", "Φ=phi_m5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_n4\n", "\n", "\n", "Φ=phi_n5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_m6\n", "\n", "\n", "Φ=phi_m7\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_n6\n", "\n", "\n", "Φ=phi_n7\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_m8\n", "\n", "\n", "Φ=phi_m9\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=pi/4\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_n8\n", "\n", "\n", "Φ=phi_n9\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "0\n", "1\n", "2\n", "3\n", "4\n", "" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c = pcvl.GenericInterferometer(N,\n", " fun_gen=lambda idx: mzi(pcvl.P(\"phi_m%d\" % idx), pcvl.P(\"phi_n%d\" % idx)),\n", " shape=pcvl.InterferometerShape.RECTANGLE,\n", " depth=N,\n", " phase_shifter_fun_gen=lambda idx: pcvl.PS(phi=pcvl.P(\"phi_r%d\" % idx)))\n", "pcvl.pdisplay(c)" ] }, { "cell_type": "markdown", "id": "f08b44d5", "metadata": {}, "source": [ "### Basinhopping algorithm" ] }, { "cell_type": "code", "execution_count": 8, "id": "c0afc0a7", "metadata": {}, "outputs": [], "source": [ "def mzi(P1,P2,alpha):\n", " mz= (pcvl.Circuit(2, name=\"mzi\")\n", " .add((0, 1), pcvl.BS(theta=(45+alpha)/180*np.pi))\n", " .add(0, pcvl.PS(P1))\n", " .add((0, 1), pcvl.BS(theta=(45+alpha)/180*np.pi))\n", " .add(0, pcvl.PS(P2)))\n", " return mz" ] }, { "cell_type": "code", "execution_count": 9, "id": "ff2dc28f", "metadata": {}, "outputs": [], "source": [ "def calculate_angle(index_unitary, U_target, alpha):\n", " start = time.time()\n", " c = pcvl.GenericInterferometer(N,\n", " fun_gen=lambda idx: mzi(pcvl.P(\"phi_m%d\" % idx), pcvl.P(\"phi_n%d\" % idx), alpha),\n", " shape=pcvl.InterferometerShape.RECTANGLE,\n", " depth=N,\n", " phase_shifter_fun_gen=lambda idx: pcvl.PS(phi=pcvl.P(\"phi_r%d\" % idx)))\n", " params = c.get_parameters()\n", "\n", " # goal is to find phis matching U0 - by minimizing fidelity\n", " infidelities = []\n", "\n", " for _ in range(n_try):\n", " init_params = np.random.randn(len(params))\n", " res = basinhopping(lambda x: infidelity(c, U_target, params, x), init_params, stepsize=0.1, niter=n_iter)\n", " infidelities.append(res.fun)\n", " \n", " return min(infidelities)" ] }, { "cell_type": "code", "execution_count": null, "id": "0f75ba07", "metadata": {}, "outputs": [], "source": [ "n_angles = (abs(angle_min)+abs(angle_max))/angle_step\n", "\n", "def discovery_unitary(index_unitary):\n", " np.random.seed(int(10000*time.time()+os.getpid()) & 0xffffffff)\n", " U_target = pcvl.Matrix.random_unitary(N)\n", " l_infidelities = []\n", " M=angle_min + math.ceil((angle_max-angle_min)/angle_step)*angle_step + 1\n", " for alpha in tqdm_notebook(range(angle_min,M,angle_step), desc = \"Angle progress bar unitary {}\".format(index_unitary+1),leave=False):\n", " l_infidelities.append(calculate_angle(index_unitary, U_target, alpha))\n", " return l_infidelities\n", "\n", "try:\n", " os.remove(\"%s-%d.log\" % (logfilemzi,N))\n", "except OSError:\n", " pass\n", "\n", "for index_unitary in tqdm_notebook(range(n_unitary), desc = \"Unitary progress bar\"):\n", " result = discovery_unitary(index_unitary)\n", " e = datetime.datetime.now()\n", " with open(\"%s-%d.log\" % (logfilemzi,N), \"a\") as f:\n", " f.write(e.strftime(\"%Y-%m-%d %H:%M:%S\")+\"\\t\") \n", " f.write(\"\\t\".join([\"%g\"%inf for inf in result]))\n", " f.write(\"\\n\")" ] }, { "cell_type": "markdown", "id": "0a40d8ba", "metadata": {}, "source": [ "## Result of the MZI-based scheme" ] }, { "cell_type": "code", "execution_count": null, "id": "38fa883b", "metadata": {}, "outputs": [], "source": [ "with open(\"mzibasednotebook-opt-5.log\", \"r\") as filin:\n", " for line in filin:\n", " print(line)" ] }, { "cell_type": "code", "execution_count": null, "id": "d5926a3c", "metadata": {}, "outputs": [], "source": [ "L=[]\n", "with open(\"mzibasednotebook-opt-5.log\", \"r\") as flog:\n", " for l in flog:\n", " L.append([float (f) for f in l.strip().split(\"\\t\")[1:]])\n", "\n", "A=np.asarray(L)\n", "print(A)\n", "print(A.shape)" ] }, { "cell_type": "code", "execution_count": null, "id": "fe4bdf97", "metadata": {}, "outputs": [], "source": [ "Lmin=[]\n", "Lmax=[]\n", "Laverage=[]\n", "\n", "for i in range(A.shape[1]):\n", " Langle = A[:,i]\n", " Laverage.append(np.average(Langle))\n", " Langle.sort()\n", " Lmin.append(np.average(Langle[0:10]))\n", " Lmax.append(np.average(Langle[-10:]))\n", "\n", "print(Laverage)\n", "print(Lmin)\n", "print(Lmax)" ] }, { "cell_type": "markdown", "id": "c8c6dd9c", "metadata": {}, "source": [ "### Graph of the infidelity as a function $\\alpha$ in degree : MZI-based" ] }, { "cell_type": "code", "execution_count": null, "id": "d4502f9e", "metadata": {}, "outputs": [], "source": [ "#data\n", "x = np.linspace(-15, 35, len(Laverage))\n", "\n", "fig, ax = plt.subplots()\n", "\n", "ax.fill_between(x, Lmin, Lmax,alpha=.5,linewidth=0)\n", "ax.plot(x,Laverage,linewidth=2)\n", "\n", "ax.set(xlim=(-15, 35),ylim=(min(Lmin), max(Lmax)))\n", "ax.set_yscale('log')\n", "\n", "\n", "plt.plot(x,Laverage,'r')\n", "plt.title('MZI-based', fontsize=20)\n", "plt.xlabel('alpha in degree', fontsize=15)\n", "plt.ylabel('infidelity 1-F',fontsize=15)\n" ] }, { "cell_type": "markdown", "id": "90dd9c90", "metadata": {}, "source": [ "## Our results" ] }, { "cell_type": "markdown", "id": "e6c23a2d", "metadata": {}, "source": [ "For matrices of size 5, we obtained this graph below." ] }, { "cell_type": "markdown", "id": "b9f496e7", "metadata": {}, "source": [ "![bs_notebook5](../_static/img/bs-notebook5.png)" ] }, { "cell_type": "markdown", "id": "2457fba0", "metadata": {}, "source": [ "The MZI-based interferometers are equally sensitive to both positive and negative values of α. The range of tolerance for a correct implementation is of several degrees. For the BS-based interferometers, infidelity behaves rather differently: while for α < 0 the performance of the two are comparable, for α > 0 the BS-based interferometers provide a correct implementation for a much larger range of α (as\n", "large as ∼ 20 degrees). Therefore, the BS-based circuit is more stable than the MZI-based circuit although the BS-based implementation fail at implementing some unitary matrices (like permutation) in the ideal case." ] }, { "cell_type": "markdown", "id": "3130c4e7", "metadata": {}, "source": [ "In the article [1], they also performed optimisation on matrices of size 10. We pushed the calculations a bit further on matrices of size 12." ] }, { "cell_type": "markdown", "id": "4a55fe6f", "metadata": {}, "source": [ "![bs_notebook12](../_static/img/bs-notebook12.png)" ] }, { "cell_type": "markdown", "id": "1c464c9d", "metadata": {}, "source": [ "We only made our computations for the BS-based scheme because we wanted to analyze the stability of this new circuit. We do not have done both yet because it is very time consuming. Indeed, for only one matrix of size 12, the computation takes about 37 hours. Since in our code, we loop over 300 matrices, we get a total time it would take to complete this calculation of roughly 11,100 hours (~460 days) of calculation in total." ] }, { "cell_type": "markdown", "id": "a516779b", "metadata": {}, "source": [ "To conclude, the BS-based circuit is a very stable scheme but not universal. MZI-based circuit is universal when BS are perfectly manufactured, although it is not possible in practice. However, as one can see on the graphs, the MZI-based circuit is much less robust to errors. In practice, it is common to have $\\alpha$ in the range of 5 degress. Thus, the MZI-based circuit is not really universal in practice.\n", "\n", "It is therefore necessary to continue to seek relevant criteria to properly evaluate the interferometer schemes." ] }, { "cell_type": "markdown", "id": "db31ccb0", "metadata": {}, "source": [ "## References\n", "\n", "> [1] Suren A Fldzhyan, M Yu Saygin, and Sergei P Kulik. Optimal design of error-tolerant reprogrammable multiport interferometers. [Optics Letters](https://doi.org/10.1364/OL.385433), 45(9):2632–2635, 2020.\n", "\n", "> [2] Michael Reck, Anton Zeilinger, Herbert J Bernstein, and Philip Bertani. Experimental realization of any discrete unitary operator. [Physical review letters](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.73.58), 73(1):58, 1994." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Boson_Bunching.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Boson Bunching\n", "\n", "A prominent example of boson bunching is the Hong-Ou-Mandel effect, where the bunching of two photons arises from a destructive quantum interference between the trajectories where they both either cross a beam splitter or are reflected. This effect takes its roots in the indistinguishability of identical photons. You can read more about it [[1]](#references).\n", "More generally one can send in $n$ photons into an optical interferometer via $m$ modes and one then measures in which modes the photons are ending up.\n", "For the example discussed here there are 7 photons sent into 7 modes and we are interested in the probability that all 7 photons end up in the first two modes. The interferometer is described by an unitary matrix and the incoming photons can be fully indistinguishable, fully distinguishable or partially distinguishable.\n", "\n", "## Introduction\n", "\n", "The paper [[2]](#references) shows that, unlike in the Hong-Ou-Mandel effect, bunching gets maximized with partially distinguishable photons. This disproofs the following conjecture:\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Conjecture 1\n", "(Generalized Bunching). Consider any input state of classically correlated photons. For any linear interferometer\n", "$\\hat{U}$ and any nontrivial subset $\\mathcal{K}$ of output modes, the probability that all photons are found in $\\mathcal{K}$ is maximal if the photons are (perfectly) indistinguishable. \n", "\n", "Evidence for this conjecture was given [[3]](#references)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The probability for such a case is given by:\n", "$$\n", "P_n(S) = perm(H \\odot S^T)\n", "$$\n", "where $n$ is the number of input photons, $S$ the distinguishability matrix and $H$ a matrix which depends on $\\hat{U}$ and $\\mathcal{K}$. The symbol $\\odot$ is the elementwise multiplication.\n", "\n", "The distinguishability matrix $S$ is defined by:\n", "$$ S_{ij} = $$ \n", "where $ tuple:\n", " # first two orthonormal vectors from which the Unitary matrix for the circuit is built\n", " v1 = np.zeros(n, dtype=np.cdouble)\n", " v1[0] = 1\n", " v1[1] = 0\n", " for i in range(2, n):\n", " v1[i] = 1/math.sqrt(2)\n", "\n", " v2 = np.zeros(n, dtype=np.cdouble)\n", " v2[0] = 0\n", " v2[1] = 1\n", " for i in range(2, n):\n", " v2[i] = np.conj(w**(i-2))/math.sqrt(2)\n", "\n", " return v1, v2 # these are the complex conjugate of the first two rows of the matrix M for n=7\n", "\n", "\n", "def normalize(vector: np.ndarray) -> np.ndarray:\n", " return vector / np.linalg.norm(vector)\n", "\n", "\n", "def make_unitary(n: int, w: complex) -> np.matrix:\n", " vector1, vector2 = create_vectors(n, w)\n", " orthonormal_basis = [normalize(vector1), normalize(vector2)]\n", " for _ in range(n-2):\n", " # Create a random n-dimensional complex vector\n", " random_vector = np.random.rand(n) + 1j*np.random.rand(n)\n", " # Subtract projections onto existing basis vectors to make it orthogonal\n", " for basis_vector in orthonormal_basis:\n", " random_vector -= np.vdot(basis_vector,\n", " random_vector) * basis_vector\n", " # Normalize the orthogonal vector to make it orthonormal\n", " orthonormal_basis.append(normalize(random_vector))\n", " # Define the unitary matrix from the calculated vectors\n", " mat = []\n", " for i in range(n):\n", " mat.append(orthonormal_basis[i])\n", " unitary_matrix = np.matrix(mat)\n", "\n", " return unitary_matrix\n", "\n", "\n", "unitary_matrix = make_unitary(n, w)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Building the Circuit\n", "\n", "Now that we have the unitary matrix we can use directly use it as a Perceval circuit. This gives a 7 modes interferometer." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# convert Unitary matrix into a Perceval circuit\n", "circuit = Unitary(Matrix(unitary_matrix))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inputting photons of different degrees of distinguishability\n", "\n", "Now that we have the circuit which represents the correct unitary matrix and therefore the correct $H$ from the conjecture, we need to feed in the right photons following the distinguishably matrix $S=A$. In the [[2]](#references) the input photons are described via polarization. In this simulation it is not specified how the photons are distinguishable. The first two photons are fully distinguishable. The other five photons are each a linear combination of the first two photons and form an equally spaced star shape when drawn on the Bloch sphere. \n", "\n", "\n", "\n", "![Blochsphere](../_static/img/Blochsphere.png)\n", "\n", "Here one can see the two fully distinguishable photons with red arrows and the five linear combinations in the equator plane." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def create_inputs(n: int, w: complex) -> tuple:\n", " # make a list of fully distinguishable photons of the form |{i}> that means the photon has a noise tag \"i\"\n", " states_dist = []\n", " for i in range(1, n+1):\n", " x = \"|{\" + str(i) + \"}>\"\n", " states_dist.append(BasicState(x))\n", " # make a list of all input photons which are superpositions of |{1}> and |{2}>\n", " # start with the pure states |{1}> and |{2}>\n", " states_par_dist = [states_dist[0], states_dist[1]]\n", " for i in range(2, n):\n", " # add the states |{1}> + w^(i-2)|{2}>\n", " x = states_par_dist[1] * w**(i-2) + states_par_dist[0]\n", " states_par_dist.append(x)\n", " # Input states\n", " # initialise the variables\n", " indistinguishable_photons = []\n", " partially_distinguishable_photons = 1\n", " distinguishable_photons = 1\n", " # fill the states\n", " for i in range(n):\n", " indistinguishable_photons.append(1) # gives the state |1,1, ... , 1>\n", " partially_distinguishable_photons = partially_distinguishable_photons * \\\n", " states_par_dist[i]\n", " distinguishable_photons = distinguishable_photons * \\\n", " states_dist[i] # gives the state |{1},{2}, ... ,{n}>\n", " return BasicState(indistinguishable_photons), partially_distinguishable_photons, distinguishable_photons\n", "\n", "\n", "# indistinguishable photons, partially distinguishable photons, fully distinguishable photons respectively\n", "indistinguishable_photons, partially_distinguishable_photons, distinguishable_photons = create_inputs(n, w)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To see if photon bunching gets amplified for partially distiguishable photons we need to compare it to the case of indistinguishable photons and for interest we are also comparing it to fully distinguishable photons. We are expecting that the probability for partially distinguishable photons will be highest followed by the indistinguishable photons and fully distinguishable photons having the lowest probability." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# simulating boson sampling\n", "simulator = Simulator(SLOSBackend())\n", "simulator.set_circuit(circuit)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The probability for all 7 indistinguishable photons ending up in the first two modes is: 0.699 %\n", "The probability for all 7 distinguishable photons ending up in the first two modes is: 0.016 %\n", "The probability for all 7 partially distinguishable photons ending up in the first two modes is: 0.751 %\n" ] } ], "source": [ "def calc_prob(distinguishable_type, n: int, simulator) -> tuple:\n", " def reverse(lst):\n", " new_lst = lst[::-1]\n", " return new_lst\n", " # initialize the probabilities\n", " probability_photons = 0\n", " probability_distribution_photons = []\n", " # summing over all cases where all photons end up in only two modes\n", " # range over half the distribution because of symmetry\n", " for i in range(math.ceil((n+1)/2)):\n", " probability = simulator.probability(distinguishable_type, BasicState([i, n-i] + [0]*(n-2)))\n", " probability_distribution_photons.append(probability)\n", " probability_photons += probability\n", " x = []\n", " for i in range(math.ceil((n+1)/2), n+1):\n", " x.append(probability_distribution_photons[i-math.ceil((n+1)/2)])\n", " probability_photons += probability_distribution_photons[i-math.ceil((n+1)/2)]\n", " probability_distribution_photons = probability_distribution_photons + reverse(x)\n", " for i in range(n+1):\n", " if probability_photons != 0:\n", " probability_distribution_photons[i] = probability_distribution_photons[i] / probability_photons\n", "\n", " return probability_distribution_photons, probability_photons\n", "\n", "\n", "# Save the Probabilities so we don't have to run the CalcProb function multiple times\n", "probability_distribution_indistinguishable, probability_indistinguishable = calc_prob(indistinguishable_photons, n, simulator)\n", "print(f\"The probability for all {n} indistinguishable photons ending up in the first two modes is: {probability_indistinguishable*100:.3f} %\")\n", "\n", "probability_distribution_distinguishable, probability_distinguishable = calc_prob(distinguishable_photons, n, simulator) # high runtime\n", "print(f\"The probability for all {n} distinguishable photons ending up in the first two modes is: {probability_distinguishable*100:.3f} %\")\n", "\n", "probability_distribution_partially_distinguishable, probability_partially_distinguishable = calc_prob(partially_distinguishable_photons, n, simulator) # high runtime\n", "print(f\"The probability for all {n} partially distinguishable photons ending up in the first two modes is: {probability_partially_distinguishable*100:.3f} %\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From the results above we can clearly see that the implemented partial distinguishablity amplified the bunching probability from 0.699% to 0,751%. This however does not proof that our set up gives the maximum bunching probability. As it only disproofs the conjecture with a counter example." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plotting the results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first plot shows two-mode bunching probabilities for the three different scenarios. Here we can see the results match our expectations.\n", "\n", "The second plot shows the photon-number probability distribution for the first two output modes. The distributions are normalized, i.e., the probabilities are conditioned on events where all 7 photons end up in the first two modes (two-mode bunching events). This plot is interesting because it shows that the shape of the distribution of indistinguishable photons is very different to the shape of the distributions of partially and fully distinguishable photons." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkgAAAGzCAYAAADUo+joAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAVmRJREFUeJzt3QmcTfX/x/EPwzC2IbKvSSFCZEt/1U8/lRb9qp+0kJS2nwgJhZR+ipQsJS20ifwq9fOTkrQhslUqUlmzkyWZGcv9P97fOrd7z9wZd8aMOzNez8fjGvfc7z33e8/5nnM/57udfIFAIGAAAAAIyv/XfwEAACAESAAAAD4ESAAAAD4ESAAAAD4ESAAAAD4ESAAAAD4ESAAAAD4ESAAAAD4ESAAAAD4ESIiJfPny2YMPPmg5yXnnnWf16tWzE8GkSZPcPli7dm266W666SYrVqyY5WT6Hv/617+y7Dvndjqu9D137NiRZeusXr26XXrppUdN9/HHH7vP1t/QMqT3Z/b413u1juPpt99+s1tuucXKly/v8tqzZ0/LC06kc1xWIECKkZ9++sluu+02O+WUU6xw4cJWokQJO+ecc+ypp56yAwcOxDp7edamTZvciXn58uUxy8PkyZNt1KhRmX7/77//7r5D6I8QkJvNnz/flendu3dbTvDvf//bBdR33HGHvfLKK3bjjTdm22cpYFcQltbj1ltvtZzq9zx+LioQ6wyciP73v//ZNddcY4UKFbJOnTq5iD4lJcU+//xzu/fee+3bb7+1CRMmWF6mILBAgQIxCZCGDBnirkobNmxosQqQVqxYkemrUp2U9B28K0JERz9y1157rTvukD3+7//+zx3b8fHxGTr+FSCpTKumqGTJkmFpV61aZfnzH99r+Y8++siaN29ugwcPzvbPOvnkk10Q5jdr1ix77bXX7O9//7vlVL/n8XMRAdJxtmbNGneSrlatmjsIK1SoEHztrrvush9//NEFUHnRkSNHXCCoGjM9gOMpLi7OPXKbQ4cOuWPnaEFHTqBAJppjOyPHfywC2m3btlndunWPyz4sWrSo3XDDDamWqwZLLQuXXXZZluUDGUMT23E2fPhw1779wgsvhAVHnlNPPdV69OgRdmA9/PDDVrNmTXeiUM3HgAEDLDk5OWIfAVV1NmnSxBISEqx+/frBqs+33nrLPdeJqXHjxrZs2bKIfU1+/vlna9u2rTtoK1asaA899JAFAoGwtI8//ri1bNnSSpcu7T5H6/vPf/6TZt8QXQWdccYZLv+6KorUB8HrN6EA0buKTExMtC5durirFP/V5913321lypSx4sWL2+WXX26//PLLUfs1aFucffbZ7v9ar1eFrRNRqO+++87OP/98K1KkiFWqVMntMz9tf11dan/pe1WpUsX69u2bar/46SpLAfC6deuCnx/aP0Mn5q5du1q5cuXcvmrQoIG99NJLYdXxuuIUXbl56/C+99dff+22n9d0qz4UN998s+3cudOOxdHKRaS+J15+/dvYK2vaZ+3bt3f/13fq06ePHT58OOz9+lFRs7NXdpXuoosussWLF6fK4/Tp011trPaHyptX1tLrg+QdN6q9bdq0qfsMbbuXX3451fq1bVu3bu3KfOXKlW3o0KE2ceLEDPXlOtp29LaXjjE1w3rHvcqk6KLq3HPPde/XMXLFFVfY999/H/Ez1Qfpn//8p/uR1bGq80pSUlJYGuX/ggsusLJly7rPUVDwzDPPpPk9PvjgA1fzqu2ktDqvhEqrHPiFlln9Vc251KhRI1imvW0aqQ+SmuJUA6vjTvnWcfjYY4+58hJqypQp7vyk84S2g8qRylNavPzrQlbHqT8vRzs+o9mH0di8ebPNnTvX/vGPfxw1mPTyPHXqVPfboGNe5UPnxQ0bNkR8TzTnuG3HeC6Ktrxm5Nw/e/Zsa9WqlUuj4+n000933zm7UIN0nP33v/91J2AFGNFQR0EVyquvvtp69+5tCxcutGHDhrlC9vbbb4elVQG77rrrXN8mXZHoANXVx/jx410huvPOO106vV8nTn/VtX6c9OOjqmUdMPqBURCgIE0nco9OMDr4rr/+elcjpJOQmgxnzJhh7dq1C8uTDpA33njDBUoKaPydNf2UL50klcelS5fa888/707eOvl5dBBpnWoyUV4/+eSTVJ8bSZ06ddz3GDRokHXr1s0duBK6L3799Ve3DXRiUl4U+N13333uxHrxxRe7NDoJ6/vrR1Xr0Xq/+eYbe/LJJ+2HH35wP9Rpuf/++23Pnj22ceNGl168TtAK/BRAaT9qe2k7TJs2zX1f/SDoB04nJP2AqW/ElVde6fIpZ555ZvAEoh9hnVx0ovSaa/X3iy++cCeijIq2XGR0nQoUmjVr5srphx9+aCNHjnQ/JvpuHp2gFdho2+tY0Gd+9tln7rvoQsCjfaEfa5Vx/RiOHj3arrrqKlu/fr0LDtKj7a3jS5/VuXNne/HFF9021w+rAi1RMKcfFG2//v37uxO+ymZGajcysh0VuCiYUfnSZ5x00kluG2k76PyhHxWVlzFjxri+izpW/MeWyq+W6VjS9tI2UfkODf5UlvQdVZ7V5KXzk7ahyrhqtEOtXr3aOnToYLfffrvbTsqjjnt9jwsvvNAyS2VYx83rr7/ujgmdJ8T78fXTj6YCVe0TneuqVq3qmui0XxRYeP37dCx07NjR/va3vwXPHzpvzps3L+wiNJSOZTV33XPPPS4I1jnXy0s0x+fR9mG0dE7VPtA5NlqPPPKIK586Xym40XZo06aN62+poD4j57isOBd9mInymt65X+cwXcxo/TpetE2VP+3PbBPAcbNnzx5dKgauuOKKqNIvX77cpb/lllvClvfp08ct/+ijj4LLqlWr5pbNnz8/uOz99993yxISEgLr1q0LLn/22Wfd8rlz5waXde7c2S3r3r17cNmRI0cC7dq1C8THxwe2b98eXP7777+H5SclJSVQr169wAUXXBC2XOvLnz9/4Ntvv0313fTa4MGDg8/1fy27+eabw9JdeeWVgdKlSwefL1myxKXr2bNnWLqbbrop1Toj+fLLL126iRMnpnqtdevW7rWXX345uCw5OTlQvnz5wFVXXRVc9sorr7jv9dlnn4W9f/z48e798+bNSzcP2qbaX36jRo1y73/11VfDtm2LFi0CxYoVC+zdu9ct075I67v69428/vrrLv2nn34aXKbvr2Vr1qxJN6/RlguVJX+ZEq3fv729dT700ENhaRs1ahRo3Lhx8LnKt9LdfffdqfKlPHiURnn58ccfg8u++uort3zMmDHpfmfvuAndNtu2bQsUKlQo0Lt37+Ayff98+fIFli1bFly2c+fOwEknnZSl29HbXiVKlHD5CNWwYcNA2bJl3eeGfk+VxU6dOqU6li6//PKw9995551uud6TXnlp27Zt4JRTTglb5m2nN998M+x8VqFCBbffPJHKgb67v7z7y++IESPS3I56r9bhefjhhwNFixYN/PDDD2Hp+vXrF4iLiwusX7/ePe/Ro4fbjocOHQpklD5T+yYzx2d6+zBaOg60bQ8fPnzUtN42r1SpUjAP8sYbb7jlTz31VIbPcVlx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po2tXr06LI0+L1++fGGPRx99NFu+HwAAyF1iHiBNnTrVevXqZYMHD7alS5dagwYNrG3btrZt27aI6efPn28dO3a0rl272rJly6x9+/busWLFimCa4cOH2+jRo238+PG2cOFCK1q0qFtnUlJS2Loeeugh27x5c/DRvXv3bP++AAAg54t5gPTEE0/Yrbfeal26dLG6deu6oKZIkSL24osvRkz/1FNP2UUXXWT33nuv1alTxx5++GE766yzbOzYscHao1GjRtkDDzxgV1xxhZ155pn28ssv26ZNm2z69Olh6ypevLiVL18++FAgBQAAENMAKSUlxZYsWeKawIIZyp/fPV+wYEHE92h5aHpR7ZCXfs2aNbZly5awNImJia7pzr9ONamVLl3aGjVqZCNGjLBDhw6lmdfk5GTbu3dv2AMAAORNBWL54Tt27LDDhw9buXLlwpbr+cqVKyO+R8FPpPRa7r3uLUsrjdx9992u5umkk05yzXb9+/d3zWyq0Ypk2LBhNmTIkEx+UwAAkJvENECKJfV78qgZLj4+3m677TYXCBUqVChVegVQoe9RDVKVKlWOW34BAMAJ0sRWpkwZi4uLs61bt4Yt13P1CYpEy9NL7/3NyDpFTXBqYlu7dm3E1xU0lShRIuwBAADyppgGSKq1ady4sc2ZMye47MiRI+55ixYtIr5Hy0PTy+zZs4Ppa9So4QKh0DSq7dFotrTWKcuXL3f9n8qWLZsF3wwAAORmMW9iU7NV586drUmTJta0aVM3Am3//v1uVJt06tTJKlWq5Jq+pEePHta6dWsbOXKktWvXzqZMmWKLFy+2CRMmuNc1n1HPnj1t6NChVqtWLRcwDRw40CpWrOimAxB11lbAdP7557uRbHp+zz332A033GClSpWK4dYAAAA5QcwDpA4dOtj27dvdxI7qRN2wYUObNWtWsJP1+vXrXc2Op2XLljZ58mQ3jH/AgAEuCNLw/Xr16gXT9O3b1wVZ3bp1s927d1urVq3cOjWxpNdcpsDqwQcfdKPTFEQpQArtYwQAAE5c+QKaOAgZpmY7TR+wZ8+eLO+P1HHe+ixdH3Kf18+pGtPPH9d9dkw/H7F315gLY50FIKa/3zGfKBIAACCnIUACAADwIUACAADwIUACAADwIUACAADwIUACAADwIUACAADwIUACAADIigBJs1QDAADkVZkKkHQb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eZAAAAByKgIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAAHwIkAAAyYNy4cVa9enUrXLiwNWvWzBYtWpRu+mnTplnt2rVd+vr169vMmTPDXg8EAjZo0CCrUKGCJSQkWJs2bWz16tVhaXbt2mXXX3+9lShRwkqWLGldu3a13377Lfh6UlKS3XTTTW79BQoUsPbt20fMS3Jyst1///1WrVo1K1SokPseL7744jFtj7yKAAkAgChNnTrVevXqZYMHD7alS5dagwYNrG3btrZt27aI6efPn28dO3Z0Ac2yZctc4KLHihUrgmmGDx9uo0ePtvHjx9vChQutaNGibp0KejwKjr799lubPXu2zZgxwz799FPr1q1b8PXDhw+74Oruu+92AVZa/vnPf9qcOXPshRdesFWrVtnrr79up59+epZtn7wkX0ChKzJs7969lpiYaHv27HERfVbqOG99lq4Puc/r51SN6eeP6z47pp+P2LtrzIWxzkKOpBqjs88+28aOHeueHzlyxKpUqWLdu3e3fv36pUrfoUMH279/vwtqPM2bN7eGDRu6gEg/wRUrVrTevXtbnz593Ov6XSlXrpxNmjTJrr32Wvv++++tbt269uWXX1qTJk1cmlmzZtkll1xiGzdudO8PpZqk3bt32/Tp08OW6z1a388//2wnnXSSnaj2Rvn7TQ0SAABRSElJsSVLloTV0OTPn989X7BgQcT3aLm/Rke1Q176NWvW2JYtW8LS6MdbgZiXRn/VrOYFR6L0+mzVOEXr3XffdetQjVWlSpXstNNOc0HZgQMHMrAVThwFYp0BAABygx07drimLNXuhNLzlStXRnyPgp9I6bXce91bll6asmXLhr2ufkaqBfLSREM1R59//rnrC/X222+773PnnXfazp07beLEiVGv50RBgAQAwAlAzYH58uWz1157zdVSyRNPPGFXX321Pf30064PE/5CExsAAFEoU6aMxcXF2datW8OW63n58uUjvkfL00vv/T1aGn8n8EOHDrmRbWl9biQaJaemNS84kjp16rh+UOrLhHAESAAARCE+Pt4aN27sRoGF1sroeYsWLSK+R8tD04tGonnpa9So4YKc0DTqRKy+RV4a/VWna/V/8nz00Ufus9VXKVrnnHOObdq0KWx6gB9++MH1ZapcuXLU6zlRECABABAlDfF/7rnn7KWXXnKjy+644w43Sq1Lly7u9U6dOln//v2D6Xv06OFGj40cOdL1U3rwwQdt8eLF9q9//cu9riavnj172tChQ10n6m+++catQyPTvLmMVMtz0UUX2a233urmXJo3b557v0akhY5g++6772z58uWuZkkjtPR/PTzXXXedlS5d2uVVaTVVwL333ms333wzzWsR0AcJAIAoadj+9u3b3cSO6iCt4foKgLxO1uvXr3c1Mp6WLVva5MmT7YEHHrABAwZYrVq13PD7evXqBdP07dvXBVma10g1Ra1atXLrVGdqj/oNKSj629/+5tZ/1VVXubmTQmnY/7p164LPGzVq5P56s/kUK1bM1V5pSgKNZlOwpHmRFJwhNeZByiTmQUJ2Yh4kxBrzIOFE//2mBgkAkOO0aH1xrLOAGFvwyXsx/Xz6IAEAAPgQIAEAAOTEACkn3hkZAACcuGIeIOXUOyMDAIATV8xHseWGOyNHwig2ZCdGseFEH8VGJ20syKZO2rliFJt3Z+TQSbWiuTOyapxCqXZI80pEc2dkBUhHuzPylVdemepzk5OT3cOjDett6Kx2cP++LF8ncpfsKFcZcSBlf0w/H7EX6zKoW2ngxLY3m8qgt96j1Q/FNEDKTXdGHjZsmA0ZMiTVctV2AVntzVhnACe8eyfEOgc40SWG3DMuO+zbty/dz2AepCipliu05kpNgerorZlINVU8sja6V+C5YcOGLG++BKJBGUSsUQazj2qOFBwdrTtNgbx8Z2SNYgtNo35Kmb0zcqFChdwjlJrpkH10UuDEgFiiDCLWKIOxq52K6Si23H5nZAAAkDfFvIlNzVadO3d2HaabNm1qo0aNSnVn5EqVKrk+QN6dkVu3bu3ujNyuXTubMmWKuzPyhAkTUt0ZWTcFVMA0cODANO+MrJFvBw8ejHhnZAAAcGKKeYCUk++MjNhQU6bmxfI3aQLHC2UQsUYZjL2Yz4MEAACQ08R8Jm0AAICchgAJAADAhwAJAADAhwAJAADAhwAplznvvPPcNAaZtXbtWjcVwvLly93zjz/+2D3XaL9Y5SmzHnzwweDkn1mZNtptF0lWbM/cRt/XuxdiZvbFTTfdFJyCI6tQzjMvL5bz0H1XvXp1N51MZumm57GYJDia/ZKZtMda5qsf4/bMyWI+zB8Z89Zbb1nBggWzbH2aNmHz5s1RzSqqk+L5559vv/76a9gJIqvzFK0+ffpY9+7dj/vnnqj046tAyH/SVfkpVaqU5WSUc3i+/PJLK1q0aFRp9eOvACE0SNDUNJdccokdb7rtiMqw7kCB44MAKZfRDXWzejbztG6vEqs8RatYsWLugeylmUB0U+m0HGv5OR4o5/CcfPLJx/T+hIQE9zjedFuu3HCs5SU0seUy/qrif//733bzzTdb8eLFrWrVqsEZxT2LFi2yRo0auUkyNVv5smXL0q0qX7dunV122WWuRkBXWWeccYbNnDnTVdnqqlr0mt6jphF/nqLN1/z5811TgJcv1UyEVglHqsb20qTVnKDvotnYlW+995xzznHfJ9Qrr7zi8qeaBM2crhsWejSZqCYV1Xt1E+JLL73Ufvrpp1T7YOXKla5GQnnXBKWffPJJuvvs888/t3PPPdedVHUVePfdd7uJTLOD9oUmQNVD31FXm5pJPnS6M20DbXPtG51wr7vuurB7E3pl4r333nO3AtJEda+++qoNGTLEvvrqK/eaHtpHkZrY7rvvPjvttNOsSJEidsopp7jP12z10Xj55Zfdtk9OTg5bria4G2+8Mc33Uc5PrHKeHn2m7sCgoFL349RdF9JqEtJxoe2rfadyrjspKN/e/tZ2veeee4JlPtI+8/ZPettc/7/++uvdPlOennzyyVTlKVJTtT7HO878zWaq4dQ6FfBpm2vS5IkTJ4a9/+eff3blWcdigwYNbMGCBcHXdu7caR07dnR3qtDr9evXt9dffz3V9jx06FC65xM/HWO33HKLy5fuIXfBBRe480ZuRICUy+ng934Q7rzzTrvjjjts1apV7rXffvvNnfzq1q3r7junA1nV9em566673I/Tp59+at9884099thj7kSjE96bb77p0mj9qup96qmnMpUv3RtPP046IJcuXWoPP/yw+1E9FjqI9SOq29B8/fXX7kSgmdRDf2j0I6AT0IwZM9xDJ/xHH3007MSqW9/o1jW6l59mWL/yyivdPfpC3Xvvvda7d2/33XRfP30XnWwi0WfqtjaaqV35mjp1qvsh0Qknu7z00ktWoEABFzRoHz3xxBP2/PPPB19XsKJtrpOWtodOvF4QEKpfv35u+3z//fd24YUXuu+sQEL7Xg81NUSiYEEn9e+++859/nPPPed+EKJxzTXXuNqqd999N7hMwdv//vc/F4hEQjk/Mct5WpRvfed33nnHPvjgAxdQavtHon2tsvnss8/a6tWr3XbT/vKaVCtXrmwPPfRQsMyn5WjbXNt73rx5rlzr3qGfffZZmnmKlgIVHWO6kNEx+swzz6Rqfrv//vvdsaCgShctCohUhiQpKcldAOnYWrFihStHugjReSMj55NIx7COWeVLx+NZZ53l7lihm8HnOppJG7lH69atAz169HD/r1atWuCGG24IvnbkyJFA2bJlA88884x7/uyzzwZKly4dOHDgQDCNXtNuX7ZsmXs+d+5c9/zXX391z+vXrx948MEHI362P22kPEWTL/315+u5554Ly9fEiRMDiYmJYZ/z9ttvuzSewYMHBxo0aOD+v3PnTvfaxx9/HDHvSlukSJHA3r17g8vuvffeQLNmzQJp2b59u1vnN998456vWbPGPX/00UeDaQ4ePBioXLly4LHHHou4jbp27Rro1q1b2Ho/++yzQP78+cO+f1bRvqhTp47b5p777rvPLUvLl19+6fK8b9++sO8wffr0sHSh2zuU0mrfpGXEiBGBxo0bp7mezp07B6644org8zvuuCNw8cUXB5+PHDkycMopp4R9p1CU8xOvnKdFZTg+Pj7wxhtvBJdpmyUkJISdN5988slg2TrttNMCKSkpEdcXmtbj32dH2+ZaXrBgwcC0adOCr+/evdu9J7Q8RTqO9Dn6vND94pWdyy67LNClS5eI+fbSPv/888Fl3377rVv2/fffp7n92rVrF+jdu3eGzifVQraR9nmJEiUCSUlJYeutWbOmO05zG2qQcrkzzzwz+H9dRarJxGsu0VWFXg+9B52uBNOj6mXd6FfV9roPkK4GszpfusL250tNBsfaP0S1IG3btnVXurrS8V/xqfpbtRseVXWHNi3pClJXWGoWUtWw0nv3AwwVug11ZaUaBG3rSFRLo9oUrx+JHsqjrtbXrFlj2aF58+ZhNQrKr76b149IV3XaRmpW0PZQbUSk76nvlRmqPVD50T7X99V9E/3rTo9uIq0r/19++cU91/bTvg39TqEo5ydmOU+rJiclJcWaNWsWts1OP/30NGs7Dhw44LaFyt3bb78drGHJiPS2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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAXc5JREFUeJzt3QncVOP///GrPUX7RmiTUihKiy1LZN9CSCUVoUKELGUvSwsVkSJ7ErJWqJBWLaSNigrt++ZOdf6P9/X7n/memXtm7rnnnvueuc+8no/HyJz7zJlznXPmXJ9zrQUcx3EMAACATxRM9g4AAAAkEsENAADwFYIbAADgKwQ3AADAVwhuAACArxDcAAAAXyG4AQAAvkJwAwAAfIXgBgAA+ArBDZBEZ511ln25/vzzT1OgQAHzxhtv5Ol+3HTTTaZ69epZrqd1LrnkEpOqpk6dao/fhx9+mLA05yXtj/YrND36N7c9+uij9ru89L5bt24mL+ia1/fpNwDkFMENUpp7wytevLj5+++/M/1dgcHxxx+flH0DUtXTTz9tPvnkE5OKUnnf4B8EN8gXMjIyTP/+/Y3fVatWzezdu9e0a9cu2bvieyNGjDDLli0zqezMM8+014P+ze0A4uGHH7bfldsi7ZuueX2/fgNAThHcIF9o2LChzYz++eefXPsOzSGbFzf3aNxSqkKFCiV1P9JBkSJFTLFixUwqK1iwoL0e9G9u2b17t/23cOHC9ruSRde8vj+0agyIB8EN8oUHH3zQHDhwIKbSm/3795snnnjC1KpVy2Zeasegz6v0J1z7kYkTJ5rGjRubQw45xLzyyiuBdg4ffPCBeeyxx0zVqlXNYYcdZq6++mqzfft2u5277rrLVKpUyRx66KGmY8eOmbb9+uuvm3POOceuo32oV6+eefnll7Pc99A2N+6+hHuFthf56quvzBlnnGFKlixp9/fiiy82ixYtyvQdempWVZ4yEv378ccfm+yaNGmSDTi1DaXto48+yrL9RqR2Fe55mDZtmmnSpIndZs2aNc2bb76Z6fPbtm0zd999t/2MjuuRRx5p2rdvbzZt2hS03sGDB81TTz1l/67tnXvuuWb58uVR29y4x/755583r776auD6OeWUU8ycOXMy7cvYsWNt2r3HMdZ2PAqkn3zySbt/JUqUMGeffXbYcxWuzc3vv/9uWrdubapUqWK/W9u47rrr7LUpWl8By+jRowPXituOxz0vixcvNjfccIMpW7asOf3004P+Fs4777xj6tSpY7+vUaNG5vvvv496LF2h24y2b5Ha3Lz00kumfv369lwcccQR5o477rDXQbjqaaVLx1LHVL/bZ599NtM+DRkyxG5P6yj9+u2/++67YdON/KtwsncAiEWNGjVsJqbSmwceeMDe5CLp3LmzvXkqGLnnnnvMrFmzTL9+/cySJUsyZeSqlrj++uvNrbfearp06WJv4C59RgGPvk8Zo26KetrXU/TWrVvtjXvmzJn2pqz969OnT+CzCmR0A73sssvsE/Fnn31mbr/9dpvp6uYcq+OOO8689dZbQct0Y+/Zs6cNnFxap0OHDqZVq1bmmWeeMXv27LH7oIxr/vz5gYxHQYkyRmXKSt/mzZttcKYMMlbKXNu0aWO6du1qv1OB3DXXXGMmTJhgzjvvPBMPHV+dr06dOtltjho1ymZ6ykh1HGXXrl02eNN5vPnmm83JJ59sg5pPP/3U/PXXX6ZChQqB7SkI1nm69957baavTK5t27b2WsiKMrqdO3faa0KZrT571VVXmZUrV9rzL1988YU9BieccII9jroetO/KUGOha0XBzUUXXWRf8+bNM+eff77Zt29f1M/p7zrHCqa7d+9uAxy1Rfv888/tdVG6dGl7Leg3oEDxlltusZ9ToOal81W7dm1bRaRAK5rvvvvOjBkzxvTo0cMGGAo2LrjgAjN79uxst3eLZd+89BvTA0bLli3NbbfdZn+vuq4VbP7444+B8yE6B9ovnatrr73WNiq///777Tm68MIL7Tq6fygdutbuvPNO8++//5pffvnFXhcK9uAjDpDCXn/9dd15nTlz5jgrVqxwChcu7PTo0SPw9xYtWjj169cPvF+wYIFdv3PnzkHbuffee+3yyZMnB5ZVq1bNLpswYULQulOmTLHLjz/+eGffvn2B5ddff71ToEAB58ILLwxav3nz5nZbXnv27MmUllatWjk1a9YMWqb918v1xx9/2O9WusM5ePCgc8kllziHHnqos2jRIrts586dTpkyZZwuXboErbtu3TqndOnSQcsbNmzoHH744c62bdsCyyZNmmS/MzQN4bjHbNy4cYFl27dvt9s86aSTAsv69u1r14t0PpXO0G1+//33gWUbNmxwihUr5txzzz2BZX369LHr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kbn6HTqZ6XpRS7qnX9L9GJD+9E6/pPsxSPf059YxyKrExkWDYgAA4CsENwAAwFcIbhKoWLFipm/fvvbfdJTu6Zd0PwakP73TL+l+DNI9/alyDNKuQTEAAPA3Sm4AAICvENwAAABfIbgBAAC+QnATwebNm02lSpXMn3/+mWvfMWHCBNOwYUM7sGA6pn/Tpk32O/766y+TirgGSH86p1/S/RhwHzT59hoguIngqaeeMpdffrmpXr26fb969Wpz8cUXmxIlStgT3atXL7N///6In586daodATnca86cOXadCy64wBQpUsS88847JtXT36NHD9OoUSPb+l0XYaxmzJhhzjnnHFOyZEk7mNOZZ55p9u7da/9WoUIF0759e9uqPhV5j8HPP/9srr/+ejvq5iGHHGKOO+4488ILL2S5DX029Pz3798/8Pf8cg3oBqd91cigugZ0HLp16xbzHG0ZGRn2ulH6FyxYkO/S76VjoYFAlZZt27ZF3cZll11mjj76aFO8eHFz+OGHm3bt2tkR0vND+sMdg3D3s/fffz/i55UhdurUydSoUcP+bmrVqmV/7/v27cu318Abb7xhTjzxRHtOlRfccccdUbfx6quvmrPOOsve/8JdM/npPvjGG29EzNc2bNgQcRtbtmwxbdu2tcegTJky9prYtWtX7l4D6i2FYLt373ZKlSrlzJgxw77fv3+/c/zxxzstW7Z05s+f73z55ZdOhQoVnN69e0fcRkZGhrN27dqgV+fOnZ0aNWo4Bw8eDKw3dOhQp3Hjxk4qp1+6d+9u97Vdu3ZOgwYNYtrO9OnT7Xb69evn/Prrr87SpUudMWPGOP/++29gHS0vVqyYs3nzZieVj8HIkSOdHj16OFOnTnVWrFjhvPXWW84hhxziDBkyJOp2qlWr5jz++ONB18GuXbuC1skP18CWLVucl156yZkzZ47z559/Ot98841Tp04d5/rrr49pezp2F154oXpm2t9Qfku/1+WXXx5Iy9atW6NuZ+DAgXYbOmY//vij07x5c/tK9fRHOgZK8+uvvx50Pe/duzfiNr766ivnpptuciZOnGh/N+PHj3cqVark3HPPPSl/DMKlf8CAAc4RRxzhvPPOO87y5cudn3/+2aYpmkGDBtl7oF6Rrpn8ch/cs2dPpnytVatWTosWLaJu54ILLrD5xsyZM50ffvjBOeaYYzLd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Ho77fTNnzgwsU5CgZbNmzQoEN8oYd+zYEVinV69eNuBwMzBlaN5J/5SZKmh59tlnI86gnZ3Pec/FF198YZft3bvXvldG+MYbbzixCheEvPbaa4G/K4DSMh2HSJTxejPoSNtVIOJ1wgkn2MA7nHDHKNI508zsXroOFGyPHTvWvldGr2tWQagoSNexdgNlBYddunQJ2sY111zjXHTRRRG/V+nV9eSdPFHXoIJNl4I0zRb+3nvv2fcK7BW46Fx7z1/BggWddevW2fcKftzzLQoqFZS7wc2///5rrz8F314KgjTLtFx66aV2lmnAi2op+MqJJ54Y9F5VNqpSysl2VAyvKouaNWsGLQvdbrNmzWx1gKt58+Z2wjkVvat4Xv8ee+yx5tBDDw28VL2kNhwuVROEpiHUkiVL7La933XaaafZ4v6//vorW+ksXLiwOeWUUwLv69ata6sI9B3eqhhV+4Q7ptp3tSfR97vUtkRVZt5thMrO57zHQ98t7verSqNz58622qV///5BxzJW0bafE6pK81IV3JNPPmnT3LdvX/PLL7+YRNB1cOaZZ9pqK1WzLl682Nx+++222m/p0qX2GtM51jUsOr7e4y56H+18iarRvO2/9Bs44YQTAu8LFSpkq5bcY6ftqQpJVXDe71E1k6oWVb2mhtiqnvVej97jtnz5clv9q95p3t+NqgXdc33bbbeZ999/3/bqU7XW9OnTc3A04RcEN/CV0EabuvHrZirujdlbp68MNqvtaBvRthsLBR66+c+dO9e2+XFfygBeeOGFwHpqM+ANWlJBTtOeyO93j437/WpzsmjRIjtz8eTJk21bpY8//jhh288Jb6YuCsJWrlxp2rVrZ4NdZeJDhgwxiWpvpuDmhx9+sG3F1P7IDXgU3KjtT25cB7l9bbjtc7744oug340COLfdjXqpqT3T3Xffbf755x9z7rnn2rZtSG8EN0gbanAoelp0xTv+SzizZs0Kej9z5kzbuFFBjTIcldzoqfaYY44JemW3B48aNavRqzdI+/HHH23pihp1ZocahHob4OqJWk//+o5YuI1S9f3egFENgxVoiP4uSn92PhcrlYYpY5s0aZK56qqrUrox6VFHHWW6du1qGzHfc889ZsSIERGPUThaL9w6Cl6U4Y8dO9YGOqJ/v/nmG3uM3WWic+s97qL32T3uWdH3qMH37t27g75HDxlqxKzG6Sop8/5udD3qAcDlbVgf+rvRsfT+tjt06GDefvttM3jwYPPqq68mNC3IfwhukDZUKqKqI1VfqMRET7TeHiQ5pRuwqkkUILz33nv2qfzOO+8MZMDq0dG+fXubsf3xxx9m9uzZtheInkqzQ1UO6tGkHlGqdhg/fryt5tB3Z7fbuJ68tR1lMMpU1CtGx0jVQ7GWTqhaQD1mNLaOMtguXbrYqoROnTrZdapVq2af6DXmzsaNG+3TeCyfy8revXtt7zSVTujJXRmngqNYA7O8pkEe1QtP537evHlmypQpgX0Nd4zCURWh/qYeZJs2bQr02FPVmnpavfvuu0HBjXpOqXrKWw2lY66ecuoxpWrTgQMH2msy0aUdut7VU0xBx6+//mrTq2tNJVeq0hL9PvR71H7qWta17R3rRwG79kvB6+jRo21VlI6dflt6L3369LG/AVVhqRRPxzBVrwHkHYIbpJVRo0bZp0N1GVZmozYQiaLARRmuAoM77rjD3rjV9dWlEgWtoyd2Pbmqa60y46OPPjpb36Ou5OpCrOBIbRpUEqCAIJ5ATe0w7r//ftu1XRmg2jOoC3N2KHNq3bq1zbROPvlkm8koE1dm6+7vY489ZrurK1NTQBLL57KiErHNmzfbY6rg8dprr7VVFPquVKQSF10XyngvuOACu88vvfRS1GMUSl3hdb7btGljSyueffZZu1yB0RlnnGH/VZd6N+BR9ZSqv7xVZLruVBX6/PPP23Y0r7zyir02vaU7iaBrS+dzy5Ytts3P1VdfbauM1HXdpd+Czr8CILUjUzBz5ZVXBm3niSeesMMq6EHAPXZ6IFDXcLc0q3fv3ja9qorTdaE2OEhvBdSqONk7ASDv6eldAV4qjBwMAIlEyQ0AAPAVghsAAOArVEsBAABfoeQGAAD4CsENAADwFYIbAADgKwQ3AADAVwhuAACArxDcAAAAXyG4AQAAvkJwAwAAfIXgBgAAGD/5f9iIoryn3OjCAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def plot(n: int, probability_distribution_indistinguishable, probability_indistinguishable, probability_distribution_partially_distinguishable, probability_partially_distinguishable, probability_distribution_distinguishable, probability_distinguishable):\n", " x = []\n", " for i in range(n+1):\n", " # creates a list of all possible tuple such that a+b=n for tuple (a,b)\n", " x.append(\"({},{})\".format(i, n-i))\n", " output_modes = x\n", " colours = ['#58c4e1', '#946cba', '#383e48'] # light blue, purple and gray\n", "\n", " def add_labels(x, y):\n", " for i in range(len(x)):\n", " if y[i] > 0.0005:\n", " plt.text(i, y[i]/2, round(y[i], 5), ha='center', color='white')\n", " else:\n", " plt.text(i, 1.25*y[i], round(y[i], 5), ha='center')\n", " cases = [\"indistinguishable\",\n", " \"partially distinguishable\", \"distinguishable\"]\n", " probabilities = [probability_indistinguishable,\n", " probability_partially_distinguishable, probability_distinguishable]\n", " plt.bar(cases, probabilities, color=colours)\n", " plt.ylabel('probability')\n", " plt.title(\"Comparing the total bunching probabilities for {} photons\".format(n))\n", " add_labels(cases, probabilities)\n", " plt.show()\n", "\n", " plt.scatter(output_modes, probability_distribution_indistinguishable,\n", " label='indistinguishable', color=colours[0])\n", " plt.scatter(output_modes, probability_distribution_partially_distinguishable,\n", " label='partially distinguishable', color=colours[1])\n", " plt.scatter(output_modes, probability_distribution_distinguishable,\n", " label='distinguishable', color=colours[2])\n", " plt.ylabel('probability')\n", " plt.xlabel('number of photons in first two modes')\n", " plt.title(\"Normalized bunching distributions\")\n", " plt.legend()\n", " plt.show()\n", "\n", "\n", "plot(n, probability_distribution_indistinguishable, probability_indistinguishable, probability_distribution_partially_distinguishable,\n", " probability_partially_distinguishable, probability_distribution_distinguishable, probability_distinguishable)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To see the that this set up only works for at least 7 photons we can compare the cases of 4,5,6 and 7 photons. You can see this if you run the above for n = 4,5 and 6. Running it for 8 or higher values for n will take a long time.\n", "\n", "### For 8 photons the following holds:\n", "\n", "The probability for all 8 indistinguishable photon ending up in the first two modes is: 0.240 %\n", "\n", "The probability for all 8 distinguishable photon ending up in the first two modes is: 0.002 %\n", "\n", "The probability for all 8 partially distinguishable photon ending up in the first two modes is: 0.294 %\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## References" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "[1]\n", "Agata M. Branczyk, Hong-Ou-Mandel Interference (2017) in arXiv:1711.00080 (http://arxiv.org/abs/1711.00080)\n", "\n", "[2]\n", "Benoit Seron, Leonardo Novo and Nicolas J. Cerf, Boson bunching is not maximized by indistinguishable particles (2022) in arXiv:2203.01306 (https://arxiv.org/abs/2203.01306)\n", "\n", "[3]\n", "V. S. Shchesnovich, Universality of Generalized Bunching and Efficient Assessment of Boson Sampling (2016) in arXiv:1509.01561 (https://arxiv.org/abs/1509.01561)\n", "\n", "[4]\n", "Stephen W. Drury, A counterexample to a question of Bapat and Sunder (2016) in Electronic Journal of Linear Algebra **Vol 31** 69-70 (https://journals.uwyo.edu/index.php/ela/article/view/1631/1631)\n" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: docs/source/notebooks/Boson_sampling.ipynb ================================================ { "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "5f842448", "metadata": {}, "source": [ "# Perfect and Noisy Boson Sampling\n", "\n", "We are interested to simulate a boson sample with 14 photons and 60 modes, order of size comparable to what was done in *Boson Sampling with 20 Input Photons and a 60-Mode Interferometer in a* $10^{14}$*-Dimensional Hilbert Space* [[1]](#References)" ] }, { "cell_type": "code", "execution_count": 1, "id": "b852173d", "metadata": {}, "outputs": [], "source": [ "from collections import Counter\n", "import gzip\n", "import pickle\n", "import time\n", "\n", "import random\n", "\n", "import perceval as pcvl\n", "from perceval.algorithm import Sampler" ] }, { "attachments": {}, "cell_type": "markdown", "id": "0e5cdd67", "metadata": {}, "source": [ "We define all the needed values below. " ] }, { "attachments": {}, "cell_type": "markdown", "id": "9835d0b9", "metadata": {}, "source": [ "## Perfect Boson sampling" ] }, { "cell_type": "code", "execution_count": 2, "id": "0857c5c9", "metadata": {}, "outputs": [], "source": [ "n = 14 #number of photons at the input\n", "m = 60 #number of modes\n", "N = 5000000 #number of samplings" ] }, { "attachments": {}, "cell_type": "markdown", "id": "b65067e8", "metadata": {}, "source": [ "### Generating a Haar random Unitary with Perceval" ] }, { "cell_type": "code", "execution_count": 3, "id": "467a0d45", "metadata": {}, "outputs": [], "source": [ "Unitary_60 = pcvl.Matrix.random_unitary(m) #creates a random unitary of dimension 60" ] }, { "attachments": {}, "cell_type": "markdown", "id": "0bea1801", "metadata": {}, "source": [ "### A possible unitary circuit realization of such matrix would be the following." ] }, { "attachments": {}, "cell_type": "markdown", "id": "0b8753a7", "metadata": {}, "source": [ "Here we define a 2-mode unitary circuit that we can use to decompose the 60 mode unitary" ] }, { "cell_type": "code", "execution_count": 4, "id": "eb0c9880", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=φ_a\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=φ_b\n", "\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mzi = (pcvl.BS() // (0, pcvl.PS(phi=pcvl.Parameter(\"φ_a\")))\n", " // pcvl.BS() // (1, pcvl.PS(phi=pcvl.Parameter(\"φ_b\"))))\n", "pcvl.pdisplay(mzi)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "670392c5", "metadata": {}, "source": [ "Let us decompose the unitary into a Reck's type circuit [[2]](#References) - this makes a huge circuit..." ] }, { "cell_type": "code", "execution_count": 5, "id": "372b3d0f", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=4.740036\n", "\n", "\n", "Φ=1.006889\n", "\n", "\n", "Φ=4.356858\n", "\n", "\n", "Φ=3.60004\n", "\n", "\n", "Φ=1.556871\n", "\n", "\n", "Φ=2.647407\n", "\n", "\n", "Φ=1.574902\n", "\n", "\n", "Φ=0.160962\n", "\n", "\n", "Φ=1.813202\n", "\n", "\n", "Φ=2.877254\n", "\n", "\n", "Φ=0.969198\n", "\n", "\n", "Φ=5.596189\n", "\n", "\n", "Φ=5.920999\n", "\n", "\n", "Φ=3.534416\n", "\n", "\n", "Φ=2.474713\n", "\n", "\n", "Φ=2.435417\n", "\n", "\n", "Φ=5.613209\n", "\n", "\n", "Φ=0.396133\n", "\n", "\n", "Φ=2.106898\n", "\n", "\n", "Φ=5.11821\n", "\n", "\n", "Φ=2.838579\n", "\n", "\n", "Φ=2.432539\n", "\n", "\n", "Φ=1.297772\n", "\n", "\n", "Φ=5.48477\n", "\n", "\n", "Φ=5.214293\n", "\n", "\n", "Φ=4.996561\n", "\n", "\n", "Φ=0.407366\n", "\n", "\n", "Φ=5.368097\n", "\n", "\n", "Φ=5.274881\n", "\n", "\n", "Φ=3.910053\n", "\n", "\n", "Φ=4.226316\n", "\n", "\n", "Φ=2.45004\n", "\n", "\n", "Φ=0.136188\n", "\n", "\n", "Φ=1.02666\n", "\n", "\n", "Φ=3.57743\n", "\n", "\n", "Φ=2.826545\n", "\n", "\n", "Φ=3.040542\n", "\n", "\n", "Φ=1.609754\n", "\n", "\n", "Φ=4.079508\n", "\n", "\n", "Φ=4.506671\n", "\n", "\n", "Φ=2.164762\n", "\n", "\n", "Φ=2.450664\n", "\n", "\n", "Φ=0.247767\n", "\n", "\n", "Φ=1.746282\n", "\n", "\n", "Φ=0.031267\n", "\n", "\n", "Φ=4.458191\n", "\n", "\n", "Φ=2.868339\n", "\n", "\n", "Φ=1.515621\n", "\n", "\n", "Φ=5.134286\n", "\n", "\n", "Φ=3.788929\n", "\n", "\n", "Φ=5.752642\n", "\n", "\n", "Φ=2.077342\n", "\n", "\n", "Φ=2.441938\n", "\n", "\n", "Φ=0.029501\n", "\n", "\n", "Φ=4.591595\n", "\n", "\n", "Φ=3.767964\n", "\n", "\n", "Φ=5.636109\n", "\n", "\n", "Φ=2.507688\n", "\n", "\n", "Φ=5.012682\n", "\n", "\n", "Φ=2.494352\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.624053\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.813021\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.190076\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.740293\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.964167\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.020238\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.267743\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.105226\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.618836\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.536587\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.558171\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.602771\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.920596\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.723232\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.382337\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.336063\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.476066\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.727293\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.440286\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.350339\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.412416\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.53632\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.030274\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.528877\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.454362\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.804763\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.957366\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.908568\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.072056\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.466488\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.725317\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.9631\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.64118\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.14784\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.615226\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.013495\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.325985\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.162321\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.01594\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.297213\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.918873\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.643322\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.949462\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.931194\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.561801\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.918927\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.674844\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.159454\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.789373\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.83216\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.843545\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.794158\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.181822\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.267602\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.891758\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.598882\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.0614\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.913437\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.01149\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.96207\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.406004\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.717287\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.154331\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.534511\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.436667\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.623537\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.549112\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.95093\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.505907\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.512578\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.076887\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.01833\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.550215\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.518209\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.788004\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.729582\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.629309\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.141298\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.282511\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.815704\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.02637\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.051515\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.490449\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.099106\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.427608\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.310963\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.309649\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.840659\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.423553\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.204192\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.569103\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.571242\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.068724\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.009822\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.912349\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.172299\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.785193\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.920162\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.359913\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.715962\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.235728\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.307252\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.055019\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.771996\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.341087\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.162111\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.24871\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.972275\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.236537\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.203098\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.045054\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.623044\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.542048\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.561122\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.528853\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.274525\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.945905\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.625683\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.628795\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.057331\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.443494\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.7194\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.87154\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.712708\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.404492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.881269\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.06324\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.682113\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.89815\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.938004\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.554856\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.596561\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.03607\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.123839\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.691958\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.108811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.514744\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.324159\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.394044\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.662824\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.21985\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.016224\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.354035\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.807605\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.746923\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.658547\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.732508\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.108054\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.587025\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.098933\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.751347\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.588526\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.81251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.781979\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.349726\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.436582\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.167405\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.957376\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.247809\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.211532\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.202486\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.184107\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.687625\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.370939\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.544682\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.270238\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.304801\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.277968\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.923623\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.256366\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.144537\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.792536\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.595079\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.608993\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.981552\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.898597\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.459625\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.171391\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.892547\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.210686\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.144132\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.283881\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.717307\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.893502\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.538742\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.708059\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.808583\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.880861\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.640751\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.872178\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.029289\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.737562\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.29529\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.939529\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.826863\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.067583\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.821819\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.20867\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.741204\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.416059\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.321472\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.145165\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.172665\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.025449\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.878133\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.383929\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.339296\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.655274\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.760806\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.113177\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.568351\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.15483\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.146019\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.458834\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.403948\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.704768\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.885652\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.070345\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.348843\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.005219\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.387799\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.80311\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.523165\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.217478\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.139928\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.018709\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.095135\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.415015\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.604846\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.930225\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.022713\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.564893\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.882793\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.619425\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.719381\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.429702\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.047934\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.752872\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.712029\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.551747\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.803068\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.492741\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.625042\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.349738\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.283032\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.057965\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.390229\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.852304\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.70504\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.745558\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.143378\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.348683\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.971169\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.519687\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.692554\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.69879\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.854166\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.970401\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.498875\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.170666\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.152117\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.899301\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.499407\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.010543\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.559499\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.996128\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.886382\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.485645\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.969937\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.577957\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.783046\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.547705\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.656798\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.534596\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.37232\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.438384\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.562416\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.29025\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.687314\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.256132\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.125589\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.950845\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.99212\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.215238\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.127188\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.546143\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.584188\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.872574\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.050144\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.594677\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.247596\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.550621\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.35738\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.976154\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.618943\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.598518\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.322451\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.992678\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.404778\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.364956\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.940657\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.106675\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.685816\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.44518\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.265163\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.911538\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.037971\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.454304\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.123291\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.846845\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.589749\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.376708\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.529387\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.428086\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.821591\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.969444\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.639512\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.580155\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.449205\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.361059\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.600585\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.374632\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.980973\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.474253\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.249448\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.931126\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.208565\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.631211\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.578393\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.65324\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.613858\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.498234\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.679507\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.297941\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.961912\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.675638\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.276244\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.536448\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.371656\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.332572\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.881751\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.98349\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.370074\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.752796\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.125021\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.062288\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.235528\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.389124\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.639809\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.23378\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.580138\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.910216\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.505351\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.524251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.418194\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.240824\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.274764\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.885841\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.674707\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.745665\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.550549\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.355733\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.148288\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.505358\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.315581\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.563139\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.254728\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.52352\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.918675\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.914514\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.305376\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.808981\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.288425\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.267718\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.7575\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.415488\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.566217\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.633299\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.209828\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.563826\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.670251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.005644\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.707206\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.662113\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.348627\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.766935\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.688726\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.266638\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.011081\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.839089\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.814591\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.914019\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.60785\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.232345\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.301707\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.798325\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.524113\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.361535\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.084624\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.818193\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.784425\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.047585\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.80649\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.283134\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.177504\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.409372\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.133257\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.256702\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.974756\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.762446\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.568625\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.834516\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.141005\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.62401\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.938701\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.109804\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.817488\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.074883\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.242258\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.205298\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.077065\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.929401\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.760607\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.973005\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.582057\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.984534\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.711585\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.506939\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.232955\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.202651\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.763591\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.146266\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.798485\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.746389\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.633831\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.332216\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.873049\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.055942\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.578705\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.828946\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.350603\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.679341\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.512274\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.122829\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.325628\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.486666\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.040189\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.517717\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.750637\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.252627\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.180827\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.120147\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.398626\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.084165\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.210695\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.923073\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.058395\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.411369\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.685082\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.156699\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.242932\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.057456\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.450472\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.945534\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.573557\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.465165\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.284007\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.50415\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.376449\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.867247\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.158918\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.267006\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.168523\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.909395\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.698572\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.800154\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.745332\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.19618\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.894593\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.721457\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.520517\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.223406\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.798721\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.483611\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.453513\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.169637\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.328581\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.648821\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.206727\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.103528\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.720619\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.409192\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.930797\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.758352\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.687808\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.192986\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.700913\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.065122\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.139413\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.590547\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.342432\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.644044\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.216083\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.245624\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.458564\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.197996\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.105603\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.24119\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.123743\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.107442\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.198299\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.394325\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.368835\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.026051\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.217472\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.207699\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.492245\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.453351\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.914394\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.894763\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.078512\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.900679\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.851179\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.233637\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.782143\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.80281\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.573322\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.548164\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.648115\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.021791\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.41948\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.045207\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.647071\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.601001\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.67819\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.191447\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.221165\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.730652\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.872007\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.658966\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.759273\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.69932\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.989488\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.086421\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.027212\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.193095\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.6329\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.159974\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.411764\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.642342\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.57251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.794879\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.812644\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.632352\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.078169\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.83711\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.745069\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.917773\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.727145\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.768465\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.694883\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.337227\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.902167\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.618361\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.221566\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.86931\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.311331\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.378956\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.652999\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.20745\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.697681\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.474837\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.792412\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.576189\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.375611\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.368307\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.448576\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.252463\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.781888\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.238741\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.913925\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.508115\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.822584\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.606894\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.35843\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.566073\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.76475\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.813015\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.805645\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.868432\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.429679\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.2777\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.501771\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.48732\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.695375\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.016203\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.638247\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.985411\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.270868\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.386042\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.080071\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.111247\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.463682\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.966167\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.179441\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.539504\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.900861\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.260967\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.096269\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.527262\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.760662\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.066235\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.188159\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.581418\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.660749\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.261465\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.622149\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.013359\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.99096\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.925531\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.502626\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.727976\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.96803\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.845066\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.288331\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.939273\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.783852\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.163492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.116966\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.781762\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.775527\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.973623\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.544719\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.451423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.916256\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.242817\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.531461\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.02014\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.768645\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.984896\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.755477\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.623365\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.942521\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.33344\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.494574\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.303778\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.521896\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.426694\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.341373\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.346992\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.312784\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.766348\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.196826\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.676997\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.300167\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.744336\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.293488\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.860208\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.864122\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.232657\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.266094\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.597191\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.514465\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.196141\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.385583\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.092603\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.538129\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.175842\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.240401\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.180432\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.818989\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.226133\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.030775\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.291737\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.893237\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.605688\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.98271\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.552169\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.765633\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.662937\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.656673\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.570318\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.927243\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.164739\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.925279\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.215413\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.878891\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.08567\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.40271\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.260208\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.112116\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.052131\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.484336\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.523237\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.240339\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.318804\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.195135\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.107461\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.807485\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.49841\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.834926\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.929617\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.941287\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.412085\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.173663\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.123837\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.831629\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.395275\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.164689\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.905928\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.078693\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.832456\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.66714\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.620377\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.474617\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.615644\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.648425\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.654503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.071388\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.48917\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.728768\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.625915\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.015633\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.585146\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.676407\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.991404\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.409988\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.247724\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.110454\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.596154\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.084951\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.385168\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.80185\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.453792\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.793\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.762524\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.860807\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.76445\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.112583\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.958623\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.667706\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.936686\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.607265\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.296526\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.487371\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.189442\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.523291\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.801584\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.485321\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.134959\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.702132\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.866418\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.297762\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.202819\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.703321\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.351653\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.071577\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.159829\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.140888\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.896614\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.927939\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.097442\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.585378\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.586743\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.182339\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.998675\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.260777\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.145391\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.407059\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.158821\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.98155\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.747878\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.956932\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.475693\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.003137\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.746967\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.369882\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.61819\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.754101\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.672977\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.41945\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.168771\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.1586\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.588691\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.176404\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.589955\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.891954\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.429945\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.249134\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.566723\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.716647\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.037892\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.253531\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.055248\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.500648\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.372241\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.846542\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.718944\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.766286\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.187645\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.836573\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.06477\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.46551\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.610997\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.516586\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.082008\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.337761\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.538096\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.665973\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.26433\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.563913\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.685967\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.179347\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.373083\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.090102\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.257561\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.016509\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.961863\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.927383\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.489437\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.437482\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.881623\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.970893\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.179209\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.425625\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.035635\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.793084\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.31114\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.217818\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.609963\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.368057\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.968325\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.368163\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.392186\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.688182\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.159121\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.750488\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.493667\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.220282\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.042985\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.379438\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.198497\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.320336\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.057458\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.723036\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.051681\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.832413\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.978663\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.333915\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.910366\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.462962\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.631838\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.246407\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.981505\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.377511\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.466986\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.502783\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.236698\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.397052\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.444968\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.357626\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.942536\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.797173\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.078088\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.46044\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.088546\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.891218\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.797405\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.277355\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.665753\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.619201\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.917099\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.253734\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.242948\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.503053\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.946063\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.170366\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.566814\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.102842\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.900917\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.024459\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.331939\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.161522\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.015985\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.53166\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.321011\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.265132\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.233554\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.117123\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.065355\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.735368\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.468279\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.987127\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.941074\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.442056\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.923589\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.846541\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.059015\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.117\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.817591\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.715221\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.935055\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.345408\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.834964\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.580322\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.141883\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.697577\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.592188\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.323185\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.383679\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.570004\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.737303\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.657368\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.043757\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.523623\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.237838\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.95883\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.372923\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.446602\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.915114\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.100884\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.917627\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.590346\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.709524\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.768249\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.254721\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.536019\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.281786\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.257162\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.614344\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.075047\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.093194\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.167189\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.280917\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.115799\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.219758\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.112081\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.729642\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.905856\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.629503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.196431\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.565006\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.370587\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.296734\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.895937\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.275278\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.640633\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.580107\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.857467\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.351253\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.565073\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.8344\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.791712\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.128099\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.582199\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.069154\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.051933\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.455994\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.986226\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.850622\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.951682\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.388386\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.655555\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.72827\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.631317\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.805938\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.723176\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.160621\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.39521\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.771072\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.886204\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.948233\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.909582\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.986584\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.403869\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.87443\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.486845\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.404797\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.52813\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.124992\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.714624\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.718767\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.439907\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.156074\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.520695\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.281502\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.825687\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.670602\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.012619\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.540549\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.209309\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.069328\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.442519\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.267441\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.391788\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.186286\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.206258\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.092611\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.946821\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.00291\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.715312\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.495994\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.844197\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.061243\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.23839\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.858678\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.019119\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.738343\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.315273\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.468319\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.324779\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.8677\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.820693\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.524909\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.698206\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.070446\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.425693\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.920275\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.467557\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.352746\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.981905\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.295585\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.186072\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.680932\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.015926\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.223699\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.978726\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.523764\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.205792\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.020396\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.555076\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.467416\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.04868\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.27184\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.596041\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.250985\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.121162\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.843773\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.850668\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.39821\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.045738\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.828815\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.970207\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.8889\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.810026\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.507665\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.319868\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.190746\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.627307\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.576659\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.741023\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.954419\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.468682\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.802792\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.582184\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.285528\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.473138\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.094905\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.2135\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.422624\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.668038\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.060702\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.345017\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.373017\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.705439\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.231222\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.362027\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.039165\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.32139\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.392813\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.271586\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.953387\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.462271\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.760469\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.700142\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=74*sqrt(5)/29\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.031963\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.35378\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.710654\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.512251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.154892\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.246293\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.956326\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.106512\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.033017\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.974786\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.215189\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.212834\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.773683\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.648226\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.751026\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.948291\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.049508\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.803501\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.419878\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.145987\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.254537\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.665303\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.009629\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.655342\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.292518\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.065739\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.917401\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.994923\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.224353\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.494468\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.483215\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.787999\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.45282\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.249252\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.718029\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.557363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.716125\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.116689\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.126314\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.591801\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.852703\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.244793\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.510529\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.818354\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.960439\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.612742\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.191006\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.705154\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.974549\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.954248\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.102479\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.15759\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.51247\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.809632\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.261991\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.566972\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.241458\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.319503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.901939\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.327608\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.050811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.632817\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.816048\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.276264\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.200724\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.269244\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.267181\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.535058\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.948543\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.152858\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.542489\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.572813\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.702605\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.581907\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.04276\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.401376\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.35084\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.071348\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.958064\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.554978\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.548815\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.196314\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.20937\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.319523\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.361977\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.146453\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.056645\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.618944\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.472521\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.713798\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.177558\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.166624\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.214318\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.463454\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.946148\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.55754\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.352801\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.685975\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.825381\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.62431\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.747059\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.152015\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.327542\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.519291\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.977462\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.662432\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.970991\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.265637\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.120149\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.292851\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.476267\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.297153\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.489734\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.593579\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.961919\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.015596\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.486632\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.221045\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.607281\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.351151\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.311815\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.332774\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.403684\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.940665\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.094859\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.218105\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.161136\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.352618\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.168488\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.228001\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.530146\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.57877\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.217079\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.113365\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.002058\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.321617\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.186794\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.828423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.997874\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.008136\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.982272\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.370399\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.569165\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.353486\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.553934\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.986084\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.686472\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.22528\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.505352\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.508355\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.546356\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.147409\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.316007\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.759311\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.377839\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.649611\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.406434\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.905814\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.429288\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.761621\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.071648\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.838518\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.248735\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.103561\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.183212\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.621817\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.298917\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.415749\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.447942\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.849492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.288949\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.747394\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.086558\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.269772\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.38037\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.138938\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.743213\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.058044\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.253777\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.250474\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.119952\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.490437\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.575284\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.362133\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.345264\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.247672\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.215678\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.262461\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.300507\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.058857\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.975098\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.195447\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.222156\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.170317\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.148224\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.580419\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.264277\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.946969\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.587389\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.11648\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.730761\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.74774\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.263469\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.347481\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.081856\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.460087\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.559321\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.827119\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.222105\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.118532\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.330325\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.053671\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.89873\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.715438\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.271994\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.748398\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.649793\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.10636\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.764269\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.905033\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.810876\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.778328\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.550018\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.008703\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.954412\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.918811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.289716\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.603812\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.399638\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.925905\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.226206\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.917544\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.664302\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.078277\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.902947\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.099535\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.817867\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.901002\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.476141\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.663859\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.678944\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.514203\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.305392\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.59969\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.075885\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.312531\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.957341\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.893759\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.255006\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.04582\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.639761\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.397377\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.900479\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.771618\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.815809\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.027718\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.556896\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.080159\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.064542\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.362805\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.879026\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.16454\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.416095\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.487185\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.045062\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.820527\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.335845\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.064151\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.15367\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.87976\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.028693\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.875587\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.344002\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.465021\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.295167\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.033385\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.265448\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.025476\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.899548\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.661727\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.675897\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.266975\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.248361\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.975556\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.925604\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.084596\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.068219\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.752801\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.480577\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.505648\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.517669\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.730871\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.027589\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.429825\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.597032\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.163556\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.151762\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.942732\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.0094\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.839489\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.378653\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.079676\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.859188\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.978699\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.719802\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.478222\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.638218\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.945314\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.600309\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.251909\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.638492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.82468\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.273541\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.257758\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.447283\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.447173\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.689386\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.38241\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.211204\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.527517\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.653671\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.869573\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.185226\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.56398\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.179591\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.28816\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.133564\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.182033\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.918749\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.356386\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.961827\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.174733\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.402365\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.368021\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.291107\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.267254\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.480544\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.253624\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.710216\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.730513\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.316176\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.578641\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.759202\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.487691\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.917174\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.079166\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.181522\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.421486\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.041666\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.198007\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.229282\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.429324\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.01911\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.644316\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.400182\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.04531\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.011293\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.741958\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.324639\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.898771\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.270352\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.259928\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.161387\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.26734\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.155486\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.774241\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.850657\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.613708\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.767218\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.190868\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.876832\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.558085\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.043449\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.54795\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.027032\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.821635\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.630773\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.117823\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.793442\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.315637\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.715394\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.988065\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.74275\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.193254\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.08184\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.695308\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.735042\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.886634\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.684952\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.965293\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.006194\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.940936\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.449086\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.260845\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.886844\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.501607\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.570051\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.913159\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.881807\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.746375\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.163916\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.525236\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.660781\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.228056\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.91357\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.696644\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.497059\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.942266\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.128833\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.048642\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.28554\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.463158\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.242553\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.114806\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.366017\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.797286\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.958765\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.328925\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.974103\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.586645\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.375366\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.698545\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.123927\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.899055\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.322383\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.112121\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.101821\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.358747\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.16795\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.932859\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.018339\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.735831\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.237565\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.685753\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.959852\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.268203\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.484878\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.671388\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.096313\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.815941\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.640668\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.127081\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.462117\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.135178\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.202413\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.553743\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.317127\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.551749\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.80264\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.282585\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.236971\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.071882\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.58257\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.357716\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.213794\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.407431\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.486072\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.07885\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.67178\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.832725\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.290385\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.163874\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.313441\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.565791\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.595535\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.054175\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.842751\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.145534\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.461874\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.944251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.031125\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.169771\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.587207\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.443504\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.237317\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.902891\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.879795\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.518598\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.88676\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.338412\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.102935\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.52329\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.340526\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.46032\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.191277\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.209395\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.47489\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.131368\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.124529\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.13783\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.260901\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.00583\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.033859\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.086081\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.24197\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.390446\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.092962\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.961864\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.187975\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.188518\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.679573\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.126906\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.179952\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.676995\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.044239\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.720712\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.741141\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.766572\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.96112\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.067148\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.121298\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.909986\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.19224\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.459337\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.866048\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.471062\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.116804\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.853116\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.000577\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.492696\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.433222\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.240109\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.118164\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.840293\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.546118\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.910661\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.35527\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.886153\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.11962\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.072027\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.861241\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.500516\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.540944\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.280966\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.463077\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.139633\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.669697\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.090503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.533242\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.418695\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.352422\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.668352\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.810314\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.171379\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.18347\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.943712\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.9445\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.562365\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.663167\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.784796\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.68904\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.603824\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.900404\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.278734\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.8888\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.208504\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.859734\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.627411\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.003224\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.168511\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.962516\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.451679\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.252969\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.885958\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.220135\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.686781\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.134277\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.113912\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.445281\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.477019\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.121684\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.658007\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.088037\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.479365\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.228913\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.435037\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.012762\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.056893\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.312194\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.367887\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.117271\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.534696\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.199177\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.211349\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.337795\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.00848\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.228345\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.512647\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.709381\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.627463\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.671895\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.7481\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.672321\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.38563\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.091542\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.237402\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.3593\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.596666\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.110223\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.303192\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.328235\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.104582\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.642538\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.556031\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.773259\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.420918\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.141072\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.599313\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.946444\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.728554\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.298129\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.524132\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.259107\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.31182\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.229218\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.621579\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.023797\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.041356\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.409224\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.558272\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.679746\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.40003\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.497946\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.135449\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.539424\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.384668\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.24314\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.207826\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.387147\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.14954\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.499951\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.151896\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.583302\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.632217\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.189184\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.508557\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.098114\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.929428\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.723606\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.173859\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.63673\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.147031\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.564265\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.180738\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.807843\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.792\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.875349\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.57158\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.033537\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.528521\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.50102\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.816713\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.964467\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.278145\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.955317\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.141976\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.109646\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.446901\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.123336\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.062535\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.479354\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.692114\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.66754\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.050465\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.804676\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.597597\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.734331\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.136503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.594281\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.12847\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.486084\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.91393\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.275166\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.795718\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.838816\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.35818\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.316818\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.17921\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.946628\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.971028\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.891067\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.433476\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.162199\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.130664\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.575731\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.803694\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.581763\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.174629\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.210467\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.815523\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.682162\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.180855\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.360818\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.79596\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.403971\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.398745\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.423981\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.817911\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.088861\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.549152\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.88367\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.167925\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.670913\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.71281\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.526828\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.984397\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.328918\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.866182\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.679103\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.06219\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.734408\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.737407\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.166638\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.486399\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.753074\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.608127\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.655642\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.73154\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.418457\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.237075\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.736371\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.041953\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.886038\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.722356\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.701872\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.847717\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.457688\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.991297\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.399976\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.210905\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.366912\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.605847\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.187223\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.970605\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.027948\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.395306\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.594125\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.360521\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.72293\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.059417\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.360363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.449727\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.507597\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.272883\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.909848\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.877811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.80693\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.212583\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.99906\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.457912\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.184241\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.770363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.034732\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.662018\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.133182\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.344641\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.1092\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.152051\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.997873\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.118071\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.09244\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.998985\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.731793\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.772548\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.132432\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.135656\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.071358\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.522875\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.292297\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.858681\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.327973\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.393035\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.708199\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.546145\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.655492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.473166\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.552516\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.220302\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.415842\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.366222\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.257053\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.321342\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.192275\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.859497\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.598415\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.00035\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.792962\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.54231\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.950306\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.447788\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.297943\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.780063\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.96103\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.723464\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.563545\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.362774\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.894473\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.074638\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.718836\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.685183\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.230393\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.104803\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.685128\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.005418\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.977671\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.210364\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.762752\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.0644\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.843954\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.077606\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.684274\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.269518\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.856458\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.335485\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.690156\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.447135\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.151595\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.344395\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.322316\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.448287\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.214853\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.040905\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.036087\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.157685\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.128323\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.886298\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.942188\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.455084\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.635582\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.132238\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.189581\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.676535\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.180431\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.062597\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.873371\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.278288\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.729264\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.666422\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.691821\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.657563\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.172642\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.716946\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.25518\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.365896\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.14473\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.321071\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.937534\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.12158\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.966038\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.998499\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.154487\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.048094\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.326503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.885701\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.359432\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.386882\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.923556\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.884404\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.346363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.527727\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.723256\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.270514\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.564332\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.646126\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.49518\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.071691\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.383409\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.870749\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.826967\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.818921\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.680292\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.225089\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.724019\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.088714\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.836098\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.769263\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.572384\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.930906\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.716048\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.3231\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.849964\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.415099\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.926632\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.237064\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.477625\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.051292\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.364105\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.030385\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.69634\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.604324\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.322865\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.128139\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.396158\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.744164\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.98015\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.144715\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.668362\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.026226\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.684128\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.414467\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.290613\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.004481\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.658739\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.955033\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.166452\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.126554\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.963287\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.481533\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.030826\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.110396\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.04446\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.463492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.120818\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.560279\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.514814\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.587025\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.429503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.597671\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.919104\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.406319\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.074131\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.222656\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.223827\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.696901\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.481869\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.004342\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.20139\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.490523\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.384019\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.179885\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.323609\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.798335\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.115342\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.220682\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.916219\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.848458\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.883321\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.867251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.178521\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.513855\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.1655\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.220424\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.290384\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.696222\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.428619\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.459114\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.631995\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.748864\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.158957\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.540242\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.088217\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.019162\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.234845\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.771446\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.429316\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.486544\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.732457\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.568531\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.112244\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.625532\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.483822\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.270835\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.751011\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.674434\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.647764\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.41497\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.485377\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.37649\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.136708\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.47745\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.473762\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.417414\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.368226\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.023318\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.390798\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.211541\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.546166\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.361978\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.609579\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.088036\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.340377\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.351626\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.625808\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.412449\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.071491\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.712407\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.829403\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.228373\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.459027\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.836036\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.17932\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.716371\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.568206\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.788082\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.052169\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.103135\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.214372\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.862228\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.135075\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.0008\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.207099\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.991093\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.131003\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.794267\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.177667\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.451841\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.858566\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.860965\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.074471\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.342987\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.163729\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.688181\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.296345\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.944569\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.300877\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.421853\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.298072\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.470168\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.411013\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.664743\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.944608\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.993521\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.029191\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.202941\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.239196\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.625972\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.326238\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.144395\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.017815\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.813569\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.913449\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.574192\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.797088\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.550817\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.272858\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.044736\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.380851\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.785976\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.103615\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.688233\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.185051\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.824297\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.388589\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.33314\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.139741\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.68092\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.921244\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.110696\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.627816\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.42482\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.38196\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.129873\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.288201\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.196602\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.878423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.363618\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.341204\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.697624\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.758811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.481096\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.769022\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.004309\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.269341\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.08362\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.1019\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.914227\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.754941\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.284428\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.211627\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.840136\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.996102\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.530209\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.878563\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.275123\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.46924\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.426857\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.823692\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.961143\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.362959\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.593252\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.962234\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.73777\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.746874\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.46868\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.635671\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.284399\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.110123\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.136464\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.246147\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.773061\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.2393\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.913741\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.835505\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.033357\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.56311\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.982024\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.107062\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.515325\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.840293\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.506142\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.142827\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.220648\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.349886\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.179601\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.126792\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.641652\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.453665\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.998078\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.246037\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.382538\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.376339\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.982554\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.076314\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.295288\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.103626\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.546402\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.139363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.888334\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.044206\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.043607\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.575426\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.243082\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.435604\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.852215\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.068133\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.45898\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.023533\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.077543\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.883976\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.135299\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.960088\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.906541\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.189894\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.999507\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.137042\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.815207\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.137946\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.133973\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.223333\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.315547\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.34724\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.252083\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.262328\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.44507\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.127887\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.247652\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.177144\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.439876\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.590209\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.781569\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.145149\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.47466\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.516205\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.751096\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.720363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.788924\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.892881\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.25259\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.896114\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.362977\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.986669\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.830068\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.317917\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.969158\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.941246\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.735529\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.000559\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.552172\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.104006\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.925099\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.703433\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.968434\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.414537\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.456842\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.281573\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.842331\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.179276\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.555811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.236439\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.783119\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.181408\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.800699\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.188825\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.135801\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.585916\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.194698\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.078525\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.488824\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.175837\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.169435\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.139602\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.724591\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.95066\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.221921\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.916796\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.696456\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.89029\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.125919\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.164183\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.213393\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.907227\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.418974\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.980265\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.103323\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.420245\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.533839\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.062958\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.73426\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.247818\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.413111\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.439455\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.93664\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.095056\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.724219\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.986074\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.193419\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.255282\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.460154\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.808769\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.826041\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.216491\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.101264\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.036093\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.307586\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.662525\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.988654\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.135722\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.220064\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.665505\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.105506\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.989585\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.247716\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.193026\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.028076\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.042127\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.516095\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.165816\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.671955\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.332338\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.317553\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.779224\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.614879\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.471679\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.892729\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.871735\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.673443\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.839696\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.353567\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.775756\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.562597\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.663178\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.892918\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.287594\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.456855\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.752482\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.960147\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.564714\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.872346\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.456287\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.277425\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.957732\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.191395\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.994504\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.844472\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.472802\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.403839\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.102751\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.269057\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.394577\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.878229\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.12336\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.804608\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.709866\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.060605\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.566337\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.424381\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.086718\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.735638\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.076504\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.946912\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.092949\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.13176\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.356736\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.168818\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.970348\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.817726\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.983371\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.869662\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.485593\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.346542\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.290076\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.802343\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.162472\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.514356\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.167199\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.709628\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.702062\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.459699\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.199243\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.252248\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.652465\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.441121\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.300429\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.415387\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.084104\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.01779\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.224904\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.842387\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.313716\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.424365\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.399351\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.309371\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.946983\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.585763\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.198531\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.973526\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.016335\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.004514\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.142981\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.533251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.917691\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.005302\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.910144\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.275148\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.669947\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.036137\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.593368\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.079132\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.032548\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.455151\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.339258\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.49123\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.697516\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.846567\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.827565\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.369407\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.309582\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.928936\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.89254\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.022446\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.51335\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.539693\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.920361\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.729953\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.194582\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.692121\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.75417\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.804389\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.484027\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.204363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.411618\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.297433\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.785511\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.423478\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.848495\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.251325\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.503783\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.910762\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.945128\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.025674\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.881162\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.676684\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.787932\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.14815\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.825306\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.112952\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.630823\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.914099\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.704289\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.653921\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.221899\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.562049\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.030285\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.389659\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.077948\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.120661\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.032867\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.802658\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.30532\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.027064\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.292598\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.700714\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.313264\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.014116\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.885254\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.265045\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.136194\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.543813\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.489802\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.80425\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.382738\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.428877\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.074043\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.708923\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.80288\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.367095\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.029266\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.143285\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.509811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.507069\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.949477\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.041371\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.172724\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.771911\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.887553\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.343227\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.952192\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.01299\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.237379\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.742516\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.149497\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.783887\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.154131\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.630545\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.496928\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.46282\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.882976\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.231757\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.102792\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.184801\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.177997\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.79523\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.862581\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.658473\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.8519\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.188013\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.574613\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.427179\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.909354\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.096593\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.08519\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.154326\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.212071\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.908787\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.230042\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.79675\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.367769\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.499307\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.268895\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.424364\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.261564\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.466927\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.515325\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.505109\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.15802\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.528917\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.854272\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.752427\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.626668\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.714341\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.695415\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.257981\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.9253\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.415423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.258332\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.13092\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.655506\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.855733\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.787944\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.532013\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.70333\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.09423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.274064\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.422949\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.487274\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.473494\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.608658\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.173754\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.611292\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.349658\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.703145\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.109065\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.975945\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.367157\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.095816\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.397924\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.19466\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.241826\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.081299\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.42369\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.990529\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.666737\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.272905\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.003982\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.136432\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.487636\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.916574\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.515358\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.212802\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.131994\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.944779\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.894321\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.242634\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.289677\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.085638\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.551232\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.290916\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.969881\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.286212\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.255611\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.293161\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.48429\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.177804\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.218113\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.127781\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.100165\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.128506\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.804475\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.430095\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.582398\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.739013\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.253499\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.25794\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.07071\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.00537\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.495645\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.350077\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.670687\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.212965\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.575902\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.77344\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.622135\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.644223\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.245302\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.236401\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.779066\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.533853\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.422163\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.432311\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.381054\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.188561\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.616749\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.749377\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.356665\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.842042\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.468534\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.24799\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.531128\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.477905\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.31396\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.218777\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.201023\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.251899\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.030704\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.326779\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.633558\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.933971\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.573578\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.664599\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.450581\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.879549\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.096242\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.545202\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.295819\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.493734\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.509738\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.960566\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.096933\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.928858\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.523839\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.798545\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.622423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.270747\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.831066\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.707145\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.992685\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.251265\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.429392\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.348034\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.132277\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.536775\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.235899\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.736719\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.07881\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.207389\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.875523\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.469543\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.097262\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.243575\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.162678\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.170764\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.935989\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.371124\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.197924\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.921451\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.312802\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.963177\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.00424\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.256433\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.568153\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.97654\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.89419\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.20568\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.014039\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.938869\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.155212\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.31711\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.358127\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.208857\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.189552\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.882699\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.424345\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.981229\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.746551\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.146141\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.284454\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.643402\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.727268\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.179083\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.338197\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.895191\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.060348\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.113385\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.273103\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.108597\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.599421\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.372309\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.282907\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.249677\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.465761\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.371663\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.060063\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.983484\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.690932\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.588725\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.757216\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.111836\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.107266\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.248533\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.239675\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.32996\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.550966\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.33616\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.732989\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.525227\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.43411\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.094259\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.864972\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.885512\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.208253\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.920113\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.023089\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.223909\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.55184\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.914066\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.464836\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.865457\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.960385\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.944475\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.423686\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.587766\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.703496\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.441295\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.301504\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.726414\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.794246\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.880432\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.688113\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.225389\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.27474\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.565665\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.448897\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.911947\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.120636\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.797489\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.336459\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.123889\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.211302\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.712869\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.190826\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.704503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.745319\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.272021\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.45709\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.201648\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.658527\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.584664\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.672458\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.995709\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.392024\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.912444\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.640933\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.126974\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.870467\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.131552\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.290819\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.245029\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.492835\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.181317\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.270837\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.335233\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.269442\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.04563\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.263601\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.960495\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.170365\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.81448\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.093058\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.435466\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.25771\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.143358\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.516587\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.920853\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.838649\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.4632\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.935561\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.790363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.021156\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.503819\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.53082\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.110278\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.646234\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.892182\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.678813\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.969772\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.264886\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.79541\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.973482\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.996182\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.952659\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.982248\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.891492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.721964\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.007405\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.138164\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.878575\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.907684\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.477513\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.857502\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.634597\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.06277\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.558855\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.22258\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.636942\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.000744\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.245983\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.928028\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.64492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.205845\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.171895\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.994571\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.589733\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.94843\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.336862\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.702012\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.081881\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.403982\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.790909\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.071974\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.320865\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.626941\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.253178\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.003607\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.579646\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.789188\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.061081\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.675361\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.871811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.800087\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.968865\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.721718\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.081688\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.72053\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.637726\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.330916\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.550389\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.87372\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.295659\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.71235\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.114903\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.043225\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.065526\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.988437\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.240709\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.654197\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.8192\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.215761\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.804369\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.634348\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.355891\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.509836\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.174242\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.479779\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.005084\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.287705\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.975429\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.351839\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.943878\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.655071\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.61139\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.101972\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.657989\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.203027\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.757579\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.177979\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.701723\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.458848\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.384602\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.127606\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.726009\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.26224\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.629889\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.088525\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.686431\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.212133\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.839279\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.044938\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.445348\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.104386\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.820269\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.94091\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.191313\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.915028\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.37377\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.40947\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.154032\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.851818\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.825092\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.040949\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.752417\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.182895\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.935661\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.863117\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.093099\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.375783\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.977542\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.215333\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.431164\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.874779\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.292013\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.118414\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.245758\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.956192\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.821491\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.476542\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.290312\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.236108\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.063676\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.589925\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.564464\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.284772\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.905165\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.510069\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.232904\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.570816\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.436282\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.77196\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.886616\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.340506\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.784689\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.441286\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.306756\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.777874\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.526436\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.710083\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.853838\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.133209\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.066098\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.600569\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.522025\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.917897\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.72741\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.375035\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.34997\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.061727\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.644553\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.846173\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.075188\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.245599\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.624389\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.431097\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.938221\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.505632\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.558245\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.109532\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.743931\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.547122\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.068603\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.371404\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.651086\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.895514\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.220431\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.265427\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.624198\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.673664\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.917373\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.671212\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.410639\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.630814\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.52866\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.21056\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.633767\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.761874\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.379871\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.151001\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.575979\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.981262\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.206683\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.050252\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.405032\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.193104\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.817251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.003847\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.05594\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.137951\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.81173\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.495649\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.432166\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.752986\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.545711\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.225253\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.369259\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.187265\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.488561\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.160153\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.918369\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.558325\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.325251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.299933\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.444113\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.373052\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.382569\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.300888\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.022859\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.916925\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.739347\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.742355\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.335099\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.040611\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.052162\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.921854\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.345548\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.815945\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.624382\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.241906\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.613113\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.555941\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.247942\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.66774\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.829827\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.896846\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.642537\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.402062\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.607686\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.39467\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.880236\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.998315\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.732709\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.949008\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.7547\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.427095\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.571333\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.416928\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.056012\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.466435\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.41282\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.600771\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.721771\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.16944\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.176248\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.874388\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.266313\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.491642\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.949078\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.819677\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.784829\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.737249\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.78419\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.496508\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.510652\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.595931\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.099752\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.902772\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.346464\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.223179\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.298514\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.131561\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.903416\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.22852\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.718275\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.679757\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.432477\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.020185\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.354952\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.557966\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.448454\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.359097\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.769296\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.614522\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.041203\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.984437\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.618579\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.730276\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.532456\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.198944\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.620164\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.646049\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.573638\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.054173\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.216009\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.248725\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.187071\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.700849\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.564451\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.199362\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.229737\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.960697\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.174942\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.176012\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.702602\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.055741\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.717283\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.04567\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.669964\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.798839\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.508835\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.08793\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.111881\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.01296\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.069639\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.885349\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.558901\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.250687\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.464134\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.06876\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.235492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.959457\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.226731\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.002938\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.329055\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.752969\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.624163\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.447002\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.499216\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.79718\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.347679\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.282496\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.590212\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.965649\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.18587\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.376589\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.139356\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.898274\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.614806\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.101412\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.592536\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.129807\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.622155\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.265413\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.221121\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.04005\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.810262\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.382313\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.241905\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.969643\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.610858\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.183883\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.162078\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.126325\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.678338\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.478517\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.129893\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.286279\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.129255\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.468378\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.353139\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.832202\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.171369\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.278368\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.16154\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.171084\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.829388\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.692707\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.943425\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.354026\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.379648\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.210067\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.09034\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.515191\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.836136\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.061356\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.70272\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.141431\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.145518\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.818797\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.660762\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.614904\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.451929\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.192894\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.033842\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.725139\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.850667\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.766607\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.012016\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.99044\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.909275\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.886869\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.872002\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.209062\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.636203\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.764589\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.939177\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.458559\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.231655\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.651668\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.181962\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.574743\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.299483\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.676813\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.983363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.463992\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.609711\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.010715\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.348634\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.865991\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.109986\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.202159\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.399485\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.45487\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.424423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.154503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.884162\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.975893\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.455046\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.300929\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.853158\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.083434\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.268029\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.831379\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.144954\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.179516\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.080062\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.228926\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.682483\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.175303\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.211753\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.211458\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.573098\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.237114\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.435541\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.351096\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.459393\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.224823\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.358699\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.113586\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.059295\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.39926\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.062107\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.851063\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.168194\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.87467\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.783062\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.081497\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.39899\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.02328\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.749861\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.211164\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.146935\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.383035\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.892984\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.142428\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.727583\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.072379\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.191291\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.892176\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.571943\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.288454\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.989941\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.118619\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.321423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.071354\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.895544\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.860081\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.615733\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.829927\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.9423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.535335\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.499969\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.822623\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.542554\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.033508\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.746391\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.034178\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.317661\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.625681\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.842582\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.888803\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.343502\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.993397\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.741077\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.235516\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.18784\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.246372\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.40664\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.018713\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.709471\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.287809\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.18851\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.675044\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.779048\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.468097\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.63961\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.404514\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.320259\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.285963\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.340685\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.439548\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.558695\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.98857\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.069694\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.339771\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.694008\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.635886\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.42002\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.49275\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.524729\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.226228\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.774946\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.20867\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.478151\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.074885\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.226169\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.382665\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.439391\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.410842\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.252251\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.264902\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.174223\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.137709\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.033603\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.067122\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.44554\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.998451\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.212032\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.833977\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.51861\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.314876\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.393896\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.150658\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.79233\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.413685\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.699687\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.143848\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.594713\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.214293\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.574894\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.125465\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.70189\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.30263\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.936225\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.176803\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.611961\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.97713\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.958423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.255245\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.535552\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.182521\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.671329\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.144392\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.376815\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.167531\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.769436\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.171201\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.416204\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.127957\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.552525\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.181955\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.516069\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.721964\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.606607\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.085385\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.110418\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.195579\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.58591\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.90023\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.351038\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.837269\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.536603\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.076413\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.906104\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.33724\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.276811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.196338\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.308444\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.173834\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.814003\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.111425\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.412474\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.282978\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.993233\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.829956\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.207403\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.43699\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.558143\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.087805\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.299001\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.589615\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.781253\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.078196\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.117706\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.586163\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.451436\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.471929\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.643537\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.420719\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.626649\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.223803\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.328057\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.771579\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.123981\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.969181\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.030493\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.38616\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.034421\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.753691\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.546774\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.407088\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.105406\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.562822\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.521982\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.735831\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.513627\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.544838\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.648531\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.079051\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.68109\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.676317\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.858526\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.786787\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.651347\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.337714\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.24879\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.522795\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.565486\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.616431\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.975097\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.16087\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.727078\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.117754\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.91956\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.716664\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.543715\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.522802\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.75019\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.400735\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.0308\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.189044\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.032912\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.905348\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.469292\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.363871\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.779363\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.239286\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.4788\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.235503\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.17886\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.168204\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.645984\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.204561\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.507743\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.017841\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.046474\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.762488\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.905508\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.055438\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.086385\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.196126\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.965013\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.18708\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.153147\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.356865\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.189714\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.230813\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.643305\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.000305\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.193954\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.119997\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.725054\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.060336\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.837113\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.97217\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.66444\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.934836\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.310823\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.027387\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.409337\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.893141\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.42622\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.061256\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.082362\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.257488\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.666492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.925742\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.978891\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.308326\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.274892\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.627989\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.283076\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.738192\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.152378\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.846479\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.719195\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.123047\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.313221\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.306062\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.354085\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.39288\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.524792\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.071517\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.112995\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.223877\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.046423\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.197194\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.329939\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.948545\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.344046\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.194401\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.835858\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.407127\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.98868\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.020821\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.282492\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.434961\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.962228\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.462482\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.41784\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.576744\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.728057\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.209925\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.265796\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.176698\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.856732\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.740914\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.940742\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.885781\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.558177\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.462359\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2.694065\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.215925\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.860092\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.363577\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.858224\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.743493\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.637717\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.613906\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.24707\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.605382\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.108524\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.388773\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.983071\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.176811\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.011337\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6.139784\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.758848\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.825277\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.166006\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.267688\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.622099\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.89431\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.586863\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.006387\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.795906\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.458593\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.10116\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n", "10\n", "11\n", "12\n", "13\n", "14\n", "15\n", "16\n", "17\n", "18\n", "19\n", "20\n", "21\n", "22\n", "23\n", "24\n", "25\n", "26\n", "27\n", "28\n", "29\n", "30\n", "31\n", "32\n", "33\n", "34\n", "35\n", "36\n", "37\n", "38\n", "39\n", "40\n", "41\n", "42\n", "43\n", "44\n", "45\n", "46\n", "47\n", "48\n", "49\n", "50\n", "51\n", "52\n", "53\n", "54\n", "55\n", "56\n", "57\n", "58\n", "59\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n", "10\n", "11\n", "12\n", "13\n", "14\n", "15\n", "16\n", "17\n", "18\n", "19\n", "20\n", "21\n", "22\n", "23\n", "24\n", "25\n", "26\n", "27\n", "28\n", "29\n", "30\n", "31\n", "32\n", "33\n", "34\n", "35\n", "36\n", "37\n", "38\n", "39\n", "40\n", "41\n", "42\n", "43\n", "44\n", "45\n", "46\n", "47\n", "48\n", "49\n", "50\n", "51\n", "52\n", "53\n", "54\n", "55\n", "56\n", "57\n", "58\n", "59\n", "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "unitary_circuit_60 = pcvl.Circuit.decomposition(Unitary_60, mzi,\n", " phase_shifter_fn=pcvl.PS,\n", " shape=pcvl.InterferometerShape.TRIANGLE)\n", "\n", "pcvl.pdisplay(unitary_circuit_60)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "60a16527", "metadata": {}, "source": [ "### Running Simulation\n", "\n", "Now we choose the way to perform the simulation with Perceval. The number of photons is within what we could simulate with a `Naive` backend (see [here](https://perceval.quandela.net/docs/backends.html#naive)), however, the output space is far too big just to enumerate and store the states - so let us go with sampling using `CliffordClifford2017` backend (see [here](https://perceval.quandela.net/docs/backends.html#cliffordclifford2017))." ] }, { "cell_type": "code", "execution_count": 6, "id": "ed2250fd", "metadata": {}, "outputs": [], "source": [ "QPU = pcvl.Processor(\"CliffordClifford2017\", unitary_circuit_60)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "d0e1df5a", "metadata": {}, "source": [ "Select a random input:" ] }, { "cell_type": "code", "execution_count": 7, "id": "9b3072b2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The input state: |0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,0,1,0,0,1,1,0,1,0,1,0,1>\n" ] } ], "source": [ "#one can choose which mode he/she wants at input, or we can choose it randomly \n", "def Generating_Input(n, m, modes = None):\n", " \"This function randomly chooses an input with n photons in m modes.\"\n", " if modes == None :\n", " modes = sorted(random.sample(range(m),n))\n", " state = \"|\"\n", " for i in range(m):\n", " state = state + \"0\"*(1 - (i in modes)) +\"1\"*(i in modes)+ \",\"*(i < m-1)\n", " return pcvl.BasicState(state + \">\")\n", "\n", "input_state = Generating_Input(n, m)\n", "print(\"The input state: \", input_state)\n", "QPU.with_input(input_state)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "8318c124", "metadata": {}, "source": [ "Just to see that it outputs a statevectors of n photon(s) in m modes." ] }, { "cell_type": "code", "execution_count": 10, "id": "babc97d4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The sampled outputs are:\n", "|0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,2,0,0,0,0,0,0,0,0,2,1,0,0,2,0,0,0,0,0,1,0,0,0>\n", "|0,0,0,0,2,0,0,0,0,0,0,1,0,0,1,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,0,1,1,0,0,0,0,1,0,0,0,1,0>\n", "|0,0,1,0,0,0,0,0,1,0,0,0,1,2,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,1,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0>\n", "|0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,1,1,0,0,0,3,1,0,1,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>\n", "|1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,1,0,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0>\n", "|1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,1,0,0,0,0,0,1>\n", "|1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,0,1,0,0,0,2,0,0,0,0,1,0,0,0,0,0,0,0,0,0>\n", "|1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,0,3,0,0,1,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,1,0>\n", "|0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,3,0,0,0,0,0,0,0,0,2,0,0,0,3,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0>\n", "|1,0,0,1,0,0,0,0,0,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,1,0,0,1,0,0,0,1>\n" ] } ], "source": [ "# Keep all outputs\n", "QPU.min_detected_photons_filter(0)\n", "\n", "sampler = Sampler(QPU)\n", "\n", "print(\"The sampled outputs are:\")\n", "for out_state in sampler.samples(10)[\"results\"]:\n", " print(out_state)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "f614accc", "metadata": {}, "source": [ "We carry out the sampling, we do it N times, it will take some time, let us save the results to a file:" ] }, { "cell_type": "code", "execution_count": null, "id": "7dc4f7c8", "metadata": {}, "outputs": [], "source": [ "# if we want to launch parallel process\n", "worker_id=1\n", "\n", "#store the input and the unitary\n", "with open(\"%dphotons_%dmodes_%dsamples-worker%s-unitary.pkl\" %(n,m,N,worker_id), 'wb') as f:\n", " pickle.dump(Unitary_60, f)\n", "\n", "with open(\"%dphotons_%dmodes_%dsamples-worker%s-inputstate.pkl\" %(n,m,N,worker_id), 'w') as f:\n", " f.write(str(input_state)+\"\\n\")\n", "\n", "\n", "with gzip.open(\"%dphotons_%dmodes_%dsamples-worker%s-samples.txt.gz\" %(n,m,N,worker_id), 'wb') as f:\n", " start = time.time()\n", " for _ in range(N):\n", " f.write((str(sampler.samples(1)[\"results\"][0])+\"\\n\").encode())\n", " end = time.time()\n", " f.write(str(\"==> %d\\n\" % (end-start)).encode())\n", "f.close()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "b2c2db31", "metadata": {}, "source": [ "A little after (4 hours on a 3.1GHz Intel) - we do have 5M samples. We launched this on 32 threads for 2 days and collected 300 batches of 5M samples" ] }, { "attachments": {}, "cell_type": "markdown", "id": "f3537f2c", "metadata": {}, "source": [ "Let us analyze the K-first mode bunching on these samples" ] }, { "cell_type": "code", "execution_count": 10, "id": "fa8b1bce", "metadata": {}, "outputs": [], "source": [ "import gzip" ] }, { "cell_type": "code", "execution_count": 11, "id": "292db43a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5000000 samples\n", "Bunching Distribution: 0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t3\t0\t1\t0\t4\t7\t12\t15\t37\t17\t72\t26\t160\t202\t425\t645\t1096\t1305\t1913\t1695\t2754\t3818\t5382\t11044\t11120\t11836\t15396\t15035\t38725\t39583\t60725\t73354\t91170\t97192\t109380\t170639\t266973\t179713\t364987\t501164\t543633\t592287\t700750\t1085705\n" ] } ], "source": [ "worker_id = 1\n", "count = 0\n", "bunching_distribution = Counter()\n", "\n", "with gzip.open(\"%dphotons_%dmodes_%dsamples-worker%s-samples.txt.gz\"%(n,m,N,worker_id), \"rt\") as f:\n", " for l in f:\n", " l = l.strip()\n", " if l.startswith(\"|\") and l.endswith(\">\"):\n", " try:\n", " st = pcvl.BasicState(l)\n", " count+=1\n", " bunching_distribution[st.photon2mode(st.n-1)]+=1\n", " except Exception:\n", " pass\n", "print(count, \"samples\")\n", "print(\"Bunching Distribution:\", \"\\t\".join([str(bunching_distribution[k]) for k in range(m)]))" ] }, { "attachments": {}, "cell_type": "markdown", "id": "43f3c437", "metadata": {}, "source": [ "These numbers have been used on 300 samples for certification - see our article on Perceval for more details." ] }, { "attachments": {}, "cell_type": "markdown", "id": "b3c36044", "metadata": {}, "source": [ "## Boson sampling with non perfect sources\n", "\n", "Let us explore around performing Boson sampling with a non perfect source. We declare a source with 90% brightness and purity. Here, we would reach the limits of the simulation if we use the same input as before, as for each photon at the entrance, we multiply by 3 the number of input states (0, 1 or 2 photons at the input), leading to $3^n$ states. To define a source in a `Processor`, we have to define a more general `NoiseModel` that includes the properties of the source." ] }, { "cell_type": "code", "execution_count": 10, "id": "664a9469", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4782969" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "3 ** n" ] }, { "cell_type": "code", "execution_count": 11, "id": "34a50079", "metadata": {}, "outputs": [], "source": [ "noise_model = pcvl.NoiseModel(brightness=0.9, g2=0.1)\n", "QPU = pcvl.Processor(\"CliffordClifford2017\", unitary_circuit_60, noise_model)\n", "QPU.with_input(pcvl.BasicState([0, 1, 1] + (m - 3) * [0]))" ] }, { "attachments": {}, "cell_type": "markdown", "id": "1ac39328", "metadata": {}, "source": [ "We can see what is the source distribution, so how likely a state at the input of the linear circuit will be." ] }, { "cell_type": "code", "execution_count": 12, "id": "1a8defbd", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,{_:0},{_:0},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> 0.731684
|0,0,{_:0},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> 0.0855385
|0,{_:0},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> 0.0855385
|0,{_:0}{_:2},{_:0},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> 0.0381629
|0,{_:0},{_:0}{_:4},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> 0.0381629
|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> 0.01
|0,0,{_:0}{_:4},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> 0.0044615
|0,{_:0}{_:2},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> 0.0044615
|0,{_:0}{_:2},{_:0}{_:4},0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0> 0.0019905
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pcvl.pdisplay(QPU.source_distribution, precision=1e-7, max_v=20)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "acc6b222", "metadata": {}, "source": [ "Now we can launch again starting from cell 8 to use this imperfect QPU with the same nominal input, or from cell 7 for another random input." ] }, { "cell_type": "markdown", "id": "649045fa", "metadata": { "collapsed": false }, "source": [ "## Error Mitigation in Boson Sampling by Photon recycling" ] }, { "cell_type": "markdown", "id": "5a8726f5", "metadata": { "collapsed": false }, "source": [ "Photon recycling, our new error mitigation feature, is a statistical algorithm designed to reduce the effects of photon loss in the output distribution from a linear optics quantum circuit (Reference: [[3]](#References)). It computes an approximate probability distribution for the ideal case of _n_ photons by computing recycled probabilities from the lossy output samples. The following section demonstrates the use of this feature in _Perceval_ on a lossy simulation of Boson Sampling. A 10 mode random unitary circuit with 3 input photons and an imperfect source with low emission probability is used in the example." ] }, { "cell_type": "code", "execution_count": 5, "id": "5786674d", "metadata": {}, "outputs": [], "source": [ "Unitary_10 = pcvl.Matrix.random_unitary(10) # a random unitary of dimension 10\n", "Linear_Circuit_10 = pcvl.Circuit.decomposition(Unitary_10, mzi,\n", " phase_shifter_fn=pcvl.PS,\n", " shape=pcvl.InterferometerShape.TRIANGLE)" ] }, { "cell_type": "code", "execution_count": 11, "id": "2a1b2ba1", "metadata": {}, "outputs": [], "source": [ "noise_model = pcvl.NoiseModel(brightness=0.3) # imperfect source\n", "\n", "# Constructing a Processor with the lossy source and the 10 mode random unitary circuit\n", "lossy_qpu = pcvl.Processor(\"SLOS\", Linear_Circuit_10, noise_model)\n", "\n", "lossy_input = pcvl.BasicState([1, 1, 1] + (Linear_Circuit_10.m - 3) * [0]) # Input state with 3 photons\n", "lossy_qpu.with_input(lossy_input)\n", "lossy_qpu.min_detected_photons_filter(1)\n", "# n-2 photons in min detected filter gives the best approximate for output probabilities with photon recycling" ] }, { "cell_type": "markdown", "id": "7e83354c", "metadata": { "collapsed": false }, "source": [ "Computing and displaying the lossy output samples with photon count less than expected." ] }, { "cell_type": "code", "execution_count": 12, "id": "c2baf6e7", "metadata": {}, "outputs": [], "source": [ "# Sampler to create a lossy output sampling\n", "lossy_sampler = Sampler(lossy_qpu)\n", "lossy_output_samples = lossy_sampler.sample_count(100000)['results']" ] }, { "cell_type": "code", "execution_count": 13, "id": "3213fdeb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "|1,0,0,0,0,0,0,0,0,0> : 1738\n", "|0,1,0,0,0,0,0,0,0,0> : 11765\n", "|0,0,1,0,0,0,0,0,0,0> : 5860\n", "|0,0,0,1,0,0,0,0,0,0> : 7654\n", "|0,0,0,0,1,0,0,0,0,0> : 4582\n", "|0,0,0,0,0,1,0,0,0,0> : 4757\n", "|0,0,0,0,0,0,1,0,0,0> : 10890\n", "|0,0,0,0,0,0,0,1,0,0> : 9218\n", "|0,0,0,0,0,0,0,0,1,0> : 7273\n", "|0,0,0,0,0,0,0,0,0,1> : 3207\n", "|2,0,0,0,0,0,0,0,0,0> : 29\n", "|1,1,0,0,0,0,0,0,0,0> : 434\n", "|1,0,1,0,0,0,0,0,0,0> : 116\n", "|1,0,0,1,0,0,0,0,0,0> : 206\n", "|1,0,0,0,1,0,0,0,0,0> : 100\n", "|1,0,0,0,0,1,0,0,0,0> : 80\n", "|1,0,0,0,0,0,1,0,0,0> : 151\n", "|1,0,0,0,0,0,0,1,0,0> : 46\n", "|1,0,0,0,0,0,0,0,1,0> : 204\n", "|1,0,0,0,0,0,0,0,0,1> : 78\n", "|0,2,0,0,0,0,0,0,0,0> : 1710\n", "|0,1,1,0,0,0,0,0,0,0> : 535\n", "|0,1,0,1,0,0,0,0,0,0> : 1184\n", "|0,1,0,0,1,0,0,0,0,0> : 818\n", "|0,1,0,0,0,1,0,0,0,0> : 449\n", "|0,1,0,0,0,0,1,0,0,0> : 858\n", "|0,1,0,0,0,0,0,1,0,0> : 617\n", "|0,1,0,0,0,0,0,0,1,0> : 1385\n", "|0,1,0,0,0,0,0,0,0,1> : 547\n", "|0,0,2,0,0,0,0,0,0,0> : 412\n", "|0,0,1,1,0,0,0,0,0,0> : 1034\n", "|0,0,1,0,1,0,0,0,0,0> : 323\n", "|0,0,1,0,0,1,0,0,0,0> : 170\n", "|0,0,1,0,0,0,1,0,0,0> : 682\n", "|0,0,1,0,0,0,0,1,0,0> : 700\n", "|0,0,1,0,0,0,0,0,1,0> : 437\n", "|0,0,1,0,0,0,0,0,0,1> : 197\n", "|0,0,0,2,0,0,0,0,0,0> : 773\n", "|0,0,0,1,1,0,0,0,0,0> : 437\n", "|0,0,0,1,0,1,0,0,0,0> : 236\n", "|0,0,0,1,0,0,1,0,0,0> : 607\n", "|0,0,0,1,0,0,0,1,0,0> : 771\n", "|0,0,0,1,0,0,0,0,1,0> : 500\n", "|0,0,0,1,0,0,0,0,0,1> : 189\n", "|0,0,0,0,2,0,0,0,0,0> : 208\n", "|0,0,0,0,1,1,0,0,0,0> : 161\n", "|0,0,0,0,1,0,1,0,0,0> : 611\n", "|0,0,0,0,1,0,0,1,0,0> : 276\n", "|0,0,0,0,1,0,0,0,1,0> : 626\n", "|0,0,0,0,1,0,0,0,0,1> : 334\n", "|0,0,0,0,0,2,0,0,0,0> : 310\n", "|0,0,0,0,0,1,1,0,0,0> : 1080\n", "|0,0,0,0,0,1,0,1,0,0> : 854\n", "|0,0,0,0,0,1,0,0,1,0> : 343\n", "|0,0,0,0,0,1,0,0,0,1> : 122\n", "|0,0,0,0,0,0,2,0,0,0> : 1306\n", "|0,0,0,0,0,0,1,1,0,0> : 1537\n", "|0,0,0,0,0,0,1,0,1,0> : 960\n", "|0,0,0,0,0,0,1,0,0,1> : 317\n", "|0,0,0,0,0,0,0,2,0,0> : 894\n", "|0,0,0,0,0,0,0,1,1,0> : 936\n", "|0,0,0,0,0,0,0,1,0,1> : 317\n", "|0,0,0,0,0,0,0,0,2,0> : 207\n", "|0,0,0,0,0,0,0,0,1,1> : 387\n", "|0,0,0,0,0,0,0,0,0,2> : 111\n" ] } ], "source": [ "for out_state in lossy_output_samples:\n", " if out_state.n < 3:\n", " print(out_state, ':', lossy_output_samples[out_state])" ] }, { "cell_type": "markdown", "id": "54d271e5", "metadata": { "collapsed": false }, "source": [ "As expected, the imperfect source led to significant losses in the output sampling. Our Some of this loss can be recovered by using photon recycling as shown in the cell below to obtain a *mitigated distribution* that is closer to the ideal case." ] }, { "cell_type": "code", "execution_count": 19, "id": "c4daee57", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,0,0,0,0,0,1,1,1,0> 0.027285
|0,0,0,0,0,1,1,1,0,0> 0.026835
|0,1,0,0,0,0,1,0,1,0> 0.025997
|0,1,0,0,0,0,1,1,0,0> 0.024874
|0,1,0,1,0,0,0,0,1,0> 0.023755
|0,1,0,0,0,0,0,1,1,0> 0.023057
|0,0,0,1,0,0,1,1,0,0> 0.022692
|0,0,1,0,0,0,1,1,0,0> 0.022161
|0,1,0,1,0,0,1,0,0,0> 0.021077
|0,1,0,0,1,0,0,0,1,0> 0.020601
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from perceval.error_mitigation import photon_recycling\n", "# Ideal photon count = 3 in this case\n", "mitigated_dist = photon_recycling(lossy_output_samples, ideal_photon_count=lossy_input.n)\n", "pcvl.pdisplay(mitigated_dist, max_v=10) # displaying only the 10 highest probable states in the mitigated distribution" ] }, { "attachments": {}, "cell_type": "markdown", "id": "52ec00fc", "metadata": {}, "source": [ "## References\n", "\n", "> [1] Hui Wang, et al. Boson Sampling with 20 Input Photons and a 60-Mode Interferometer in a $10^{14}$-Dimensional Hilbert Space. [Physical Review Letters](https://link.aps.org/doi/10.1103/PhysRevLett.123.250503), 123(25):250503, December 2019. Publisher: American Physical Society.\n", "\n", "> [2] Michael Reck, Anton Zeilinger, Herbert J Bernstein, and Philip Bertani. Experimental realization of any discrete unitary operator. [Physical review letters](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.73.58), 73(1):58, 1994.\n", "\n", "\n", "> [3] James Mills and Rawad Mezher. Mitigating photon loss in linear optical quantum circuits: classical postprocessing methods outperforming postselection. [arxiv](https://doi.org/10.48550/arXiv.2405.02278)." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Circuit_Tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "275dc7b20c7a685e", "metadata": {}, "source": [ "# Optical Circuits" ] }, { "cell_type": "markdown", "id": "9939669dd5f675f", "metadata": {}, "source": [ "This tutorial covers Perceval Circuit build, display and usage.\n", "\n", "In Perceval, a *circuit* represents a setup of linear optics (LO) components, used\n", "to guide and act on photons. Simple examples of circuits are common optical devices such as beam\n", "splitters or phase shifters.\n", "\n", "A circuit has a fixed number of *spatial modes* (sometimes also called\n", "*paths*) $m$, which is the same for input as for output\n", "spatial modes.\n", "\n", "In particular, note that:\n", "\n", "* *single photon sources* aren't circuits, since they do not have input spatial\n", " modes (they don't guide or act on incoming photons, but *produce*\n", " photons that are sent into a circuit),\n", "* *photon detectors* aren't circuits either, for similar reasons" ] }, { "cell_type": "markdown", "id": "344937564c8718a0", "metadata": {}, "source": [ "## I. LO-components\n", "\n", "The linear optics components are the elementary blocks which act on Perceval quantum states.\n", "\n", "It's important to know how to handle the most basic components and understand their effects.\n", "\n", "At first, let's see what's possible with a `PERM` instance (permutation), a `BS` (beam splitter) and a `PS` (phase shifter).\n", "\n", "

All circuits and components can be displayed with `pcvl.pdisplay`

" ] }, { "cell_type": "code", "execution_count": 1, "id": "993339f1eecb9630", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "PERM\n", "PERM([2, 0, 1])\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}0 & 1 & 0\\\\0 & 0 & 1\\\\1 & 0 & 0\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import perceval as pcvl\n", "import numpy as np\n", "from perceval.components import PS, BS, PERM\n", "\n", "## Permutation\n", "perm = PERM([2, 0, 1])\n", "\n", "print(perm.name)\n", "print(perm.describe())\n", "pcvl.pdisplay(perm.definition())\n", "pcvl.pdisplay(perm)" ] }, { "cell_type": "code", "execution_count": 2, "id": "91dec3b5a482de7e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "PS\n", "PS(phi=pi)\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}e^{i \\left(max_{error} \\operatorname{Uniform}{\\left(-1,1 \\right)} + \\phi\\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=pi\n", "\n", "0\n", "0\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## Phase shifter\n", "ps = PS(phi=np.pi)\n", "\n", "print(ps.name)\n", "print(ps.describe())\n", "pcvl.pdisplay(ps.definition())\n", "pcvl.pdisplay(ps) # A pdisplay call on a circuit/processor needs to be the last line of a cell in a notebook" ] }, { "cell_type": "code", "execution_count": 3, "id": "97932b67e0b75319", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "BS.Rx() unitary matrix\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}e^{i \\left(\\phi_{tl} + \\phi_{tr}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)} & i e^{i \\left(\\phi_{bl} + \\phi_{tr}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\i e^{i \\left(\\phi_{br} + \\phi_{tl}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)} & e^{i \\left(\\phi_{bl} + \\phi_{br}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "BS.H() unitary matrix\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}e^{i \\left(\\phi_{tl} + \\phi_{tr}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)} & e^{i \\left(\\phi_{bl} + \\phi_{tr}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\e^{i \\left(\\phi_{br} + \\phi_{tl}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)} & - e^{i \\left(\\phi_{bl} + \\phi_{br}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "BS.Ry() unitary matrix\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}e^{i \\left(\\phi_{tl} + \\phi_{tr}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)} & - e^{i \\left(\\phi_{bl} + \\phi_{tr}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\e^{i \\left(\\phi_{br} + \\phi_{tl}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)} & e^{i \\left(\\phi_{bl} + \\phi_{br}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "BS displays its convention as a small label\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Ry\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## Beam splitters\n", "bs_rx = BS.Rx() # By default, a beam splitter follows the Rx gate convention, so bs=BS() has the same matrix\n", "\n", "# But other conventions exist too:\n", "bs_h = BS.H()\n", "bs_ry = BS.Ry()\n", "\n", "## Check the difference in the unitary definition:\n", "print(\"BS.Rx() unitary matrix\")\n", "pcvl.pdisplay(bs_rx.definition())\n", "print(\"BS.H() unitary matrix\")\n", "pcvl.pdisplay(bs_h.definition())\n", "print(\"BS.Ry() unitary matrix\")\n", "pcvl.pdisplay(bs_ry.definition())\n", "print(\"BS displays its convention as a small label\")\n", "pcvl.pdisplay(bs_ry)" ] }, { "cell_type": "code", "execution_count": 4, "id": "5362e58546be0273", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}e^{0.392699081698724 i}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}0.92388 + 0.382683 i\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "A Phase Shifter with a symbolic value for phi:\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}e^{i \\psi}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# You can ask for the symbolic matrix value of your component with the attribute U\n", "my_ps = PS(phi=np.pi/8)\n", "pcvl.pdisplay(my_ps.U)\n", "# And for the numerical value with the method compute_unitary\n", "pcvl.pdisplay(my_ps.compute_unitary())\n", "\n", "# - by using the syntax pcvl.P to create a symbolic variable\n", "# (note that you cannot compute the numerical value of your component anymore)\n", "print(\"A Phase Shifter with a symbolic value for phi:\")\n", "ps = PS(phi=pcvl.P(r'\\psi')) # Note the use of a raw string to be able to get a Latex visualization of the variable\n", "pcvl.pdisplay(ps.U)" ] }, { "cell_type": "code", "execution_count": 5, "id": "25d4e21b9c871e2e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A default Beam Splitter:\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\frac{\\sqrt{2}}{2} & \\frac{\\sqrt{2} i}{2}\\\\\\frac{\\sqrt{2} i}{2} & \\frac{\\sqrt{2}}{2}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "A Beam Splitter with a numerical value for theta:\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\cos{\\left(5 \\right)} & i \\sin{\\left(5 \\right)}\\\\i \\sin{\\left(5 \\right)} & \\cos{\\left(5 \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}0.283662 & - 0.958924 i\\\\- 0.958924 i & 0.283662\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "A Phase Shifter with a symbolic value for phi:\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}e^{i \\psi}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "A Beam Splitter with a symbolic variable...\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\cos{\\left(\\frac{toto}{2} \\right)} & i \\sin{\\left(\\frac{toto}{2} \\right)}\\\\i \\sin{\\left(\\frac{toto}{2} \\right)} & \\cos{\\left(\\frac{toto}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "... set to a numerical value\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}0.283662 & - 0.958924 i\\\\- 0.958924 i & 0.283662\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# If you do it for a Beam Splitter, you can see that, by default, theta = pi/2 and the phi angles are 0\n", "print(\"A default Beam Splitter:\")\n", "pcvl.pdisplay(BS().compute_unitary()) # this is a balanced beam splitter\n", "print(\"\")\n", "\n", "# To control the value of the parameters of a component, several choices are possible:\n", "# - by setting a numerical value during the component creation\n", "print(\"A Beam Splitter with a numerical value for theta:\")\n", "bs_rx = BS.Rx(theta=10)\n", "pcvl.pdisplay(bs_rx.U)\n", "pcvl.pdisplay(bs_rx.compute_unitary())\n", "print(\"\")\n", "\n", "# - by using the syntax pcvl.P (or its alias pcvl.Parameter) to create a symbolic variable\n", "# (note that you cannot compute the numerical value of your component anymore)\n", "print(\"A Phase Shifter with a symbolic value for phi:\")\n", "ps = PS(phi=pcvl.P(r'\\psi'))\n", "pcvl.pdisplay(ps.U)\n", "print(\"\")\n", "\n", "# - you can still modify the value of a symbolic variable after its creation\n", "# This is not true for a numerical variable!\n", "print(\"A Beam Splitter with a symbolic variable...\")\n", "bs_rx = BS(theta=pcvl.P('toto'))\n", "pcvl.pdisplay(bs_rx.U)\n", "bs_rx.assign({'toto': 10})\n", "print(\"... set to a numerical value\")\n", "pcvl.pdisplay(bs_rx.compute_unitary())" ] }, { "cell_type": "code", "execution_count": 6, "id": "b63da7b78ae01866", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}e^{i \\left(\\phi_{tl} + \\phi_{tr}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)} & i e^{i \\left(\\phi_{bl} + \\phi_{tr}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)}\\\\i e^{i \\left(\\phi_{br} + \\phi_{tl}\\right)} \\sin{\\left(\\frac{\\theta}{2} \\right)} & e^{i \\left(\\phi_{bl} + \\phi_{br}\\right)} \\cos{\\left(\\frac{\\theta}{2} \\right)}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=phi_tl\n", "\n", "\n", "Φ=phi_bl\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=theta\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_tr\n", "\n", "\n", "Φ=phi_br\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## to understand the conventions, you can note that a BS.Rx with the 4 phases phi (top left/right and bottom left/right) can be represented like that\n", "\n", "## For this cell, we needed the syntax to builds circuits... Good transition !\n", "\n", "bs_rx_circuit=pcvl.Circuit(2) // (0, PS(phi=pcvl.P(\"phi_tl\"))) // (1, PS(phi=pcvl.P(\"phi_bl\"))) // BS(theta=pcvl.P('theta')) // (0, PS(phi=pcvl.P(\"phi_tr\"))) // (1, PS(phi=pcvl.P(\"phi_br\")))\n", "\n", "pcvl.pdisplay(bs_rx_circuit.U)\n", "\n", "# we can check it's the same as bs_rx.definition()\n", "pcvl.pdisplay(bs_rx_circuit)" ] }, { "cell_type": "markdown", "id": "baed21bbcbe62da4", "metadata": {}, "source": [ "## II. LO-circuits\n", "\n", "From the LO-components, we can build a LO-circuit, i.e. a sequence of those components acting on our different modes.\n", "\n", "

\n", "A *LO circuit* (just called \"circuit\" here) isn't the same as a\n", "*quantum circuit*. Quantum circuits act on *qubits*, i.e. abstract systems in a 2-dimensional\n", "Hilbert space (or \"computational space\"); while optical circuits act on *photons*\n", "distributed in spatial modes (in the \"Fock space\"). It is possible to simply encode\n", "qubits with photons in an optical circuit; some encodings are\n", "presented in a later tutorial.

\n", "\n", "\n", "\n", "\n", "
![grover-circuit.png](../_static/img/grover-circuit.png)![grover-perceval.png](../_static/img/grover-perceval.png)
\n", "Optimized Grover algorithm (on the left) converted to a Perceval circuit (on the right).\n", "\n", "### 1. Syntax" ] }, { "cell_type": "code", "execution_count": 7, "id": "379106e56a5cf643", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\frac{\\sqrt{2} e^{1.5707963267949 i}}{2} & \\frac{\\sqrt{2} i e^{1.5707963267949 i}}{2} & 0\\\\\\frac{i e^{i \\phi}}{2} & \\frac{e^{i \\phi}}{2} & \\frac{\\sqrt{2} i}{2}\\\\- \\frac{e^{i \\phi}}{2} & \\frac{i e^{i \\phi}}{2} & \\frac{\\sqrt{2}}{2}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "Φ=phi\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "circuit = pcvl.Circuit(3) # Create an empty 3 mode circuit\n", "\n", "circuit.add(0, BS()) # The beam splitter is added to the circuit on mode 0 and 1\n", " # even though only the first mode is required in `add` method\n", "circuit.add(0, PS(phi=np.pi/2)).add(1, PS(phi=pcvl.P('phi'))).add(1, BS())\n", "\n", "# Equivalent syntax:\n", "# circuit // BS() // PS(phi=np.pi/2) // (1, PS(phi=pcvl.P('phi'))) // (1, BS())\n", "\n", "pcvl.pdisplay(circuit.U)\n", "pcvl.pdisplay(circuit)" ] }, { "cell_type": "markdown", "id": "c7dfe008a08f3fab", "metadata": {}, "source": [ "In the circuit rendering above, the red lines, corresponding to spatial modes, are representing optical\n", "fibers on which photons are sent from the left to the right.\n", "\n", "The syntax ``pcvl.P('phi')`` allows you to use parameters in the circuit, where you can assign a value or not. The behavior of the parameters of a circuit is similar to the case of the components.\n", "\n", "For instance, you can use :" ] }, { "cell_type": "code", "execution_count": 8, "id": "e312d4d89758b996", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Parameter(name='phi', value=None, min_v=0.0, max_v=6.283185307179586)]\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "Φ=pi\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "params = circuit.get_parameters()\n", "print(params) # list of the parameters\n", "\n", "# the value is not set, but we can change that with:\n", "params[0].set_value(np.pi)\n", "pcvl.pdisplay(circuit)" ] }, { "cell_type": "markdown", "id": "371e62737ee99bb6", "metadata": {}, "source": [ "### 2. Mach-Zehnder Interferometers\n", "\n", "The beamsplitter's angle $\\theta$ can also be defined as a parameter.\n", "\n", "However, as the reflexivity depends on the mirror, it's hard to have adaptibility on the angle.\n", "Therefore, in practice, we use a [Mach-Zehnder Interferometer](https://en.wikipedia.org/wiki/Mach%E2%80%93Zehnder_interferometer).\n", "\n", "The beamsplitter with a parameterised $\\theta$ is therefore implemented with a parameterised phase shifter $\\phi$ between two fixed beamsplitters.\n", "\n" ] }, { "cell_type": "code", "execution_count": 9, "id": "827e5e9c11411c41", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "## Exercise: build a circuit implementing the mzi\n", "\n", "## Solution:\n", "mzi = pcvl.Circuit(2) // BS() // (1,PS(phi=pcvl.P(\"phi\"))) // BS()\n", "pcvl.pdisplay(mzi)\n", "\n", "\n", "## Exercise: Check that the parameterised phase allows you to change the reflexivity of your MZI\n", "\n", "## Solution:\n", "import matplotlib.pyplot as plt\n", "\n", "X = np.linspace(0, 2*np.pi, 1000) # We create a list of all different values for theta\n", "Y = []\n", "for theta in X:\n", " phase = mzi.get_parameters()[0]\n", " phase.set_value(theta)\n", " Y.append(abs(mzi.compute_unitary()[0,0])**2) # compute_unitary is numerical, so far faster that mzi.U, which uses symbolic expressions.\n", "\n", "plt.plot(X, Y)\n", "plt.xlabel(\"phi\")\n", "plt.ylabel(\"R\")\n", "plt.show()\n", "\n", "## Note: If you need to create a BS directly from the reflexivity value, please use:\n", "## BS(BS.r_to_theta(reflectivity_value))\n", "## However, be aware that only theta value is stored inside the BS object" ] }, { "cell_type": "markdown", "id": "1b86a736d827c8cd", "metadata": {}, "source": [ "### 3. Universal Circuits\n", "\n", "An operation on the modes of our circuit can also be expressed as a unitary matrix.\n", "\n", "For three modes, the unitary $U=\\begin{pmatrix}\n", "a_{1,1} & a_{1,2} & a_{1,3}\\\\\n", "a_{2,1} & a_{2,2} & a_{2,3} \\\\\n", "a_{3,1} & a_{3,2} & a_{3,3}\n", "\\end{pmatrix}$ performs the following operation on the Fock state basis:\n", "\n", "$$\\begin{array}{rcl}\n", "|1,0,0\\rangle & \\mapsto& a_{1,1}|1,0,0\\rangle + a_{1,2}|0,1,0\\rangle + a_{1,3}|0,0,1\\rangle\\\\\n", "|0,1,0\\rangle & \\mapsto& a_{2,1}|1,0,0\\rangle + a_{2,2}|0,1,0\\rangle + a_{2,3}|0,0,1\\rangle\\\\\n", "|0,0,1\\rangle & \\mapsto& a_{3,1}|1,0,0\\rangle + a_{3,2}|0,1,0\\rangle + a_{3,3}|0,0,1\\rangle\n", "\\end{array}$$" ] }, { "cell_type": "markdown", "id": "f6d508c7a906c732", "metadata": {}, "source": [ "Since 1994, we know that any $U$ on the modes can be implemented as an LO-circuit [Reck's et al](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.73.58).\n", "\n", "This decomposition can be done easily in Perceval using beamsplitters and phase-shifters as follows." ] }, { "cell_type": "code", "execution_count": 10, "id": "84057abdefd0967a", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=3.470785\n", "\n", "\n", "Φ=3.855175\n", "\n", "\n", "Φ=3.790181\n", "\n", "\n", "Φ=5.832253\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=2.53698\n", "Θ=8.697508\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=1.955288\n", "Θ=4.92665\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=4.845186\n", "Θ=7.650621\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=1.445961\n", "Θ=10.421964\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=4.56874\n", "Θ=0.894228\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=1.626368\n", "Θ=8.638851\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## From any unitary\n", "n = 4\n", "U = pcvl.Matrix.random_unitary(n)\n", "\n", "decomposed_circuit = pcvl.Circuit.decomposition(U, BS(theta=pcvl.P('theta'), phi_tr=pcvl.P('phi')), phase_shifter_fn=PS)\n", "pcvl.pdisplay(decomposed_circuit)" ] }, { "cell_type": "code", "execution_count": 11, "id": "bff6374dc7cf4db", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The error between the two unitaries is 1.1428982927935346e-08\n" ] } ], "source": [ "print(\"The error between the two unitaries is\", np.linalg.norm(U - decomposed_circuit.compute_unitary()))" ] }, { "cell_type": "code", "execution_count": 12, "id": "daf77b8d5ca300ee", "metadata": {}, "outputs": [], "source": [ "## Exercise: decompose your unitary with only phase shifters and balanced beamsplitters.\n", "\n", "## Solution:\n", "mzi = pcvl.Circuit(2) // BS() // PS(pcvl.P(\"phi1\")) // BS() // PS(pcvl.P(\"phi2\"))\n", "\n", "circuit_u = pcvl.Circuit.decomposition(U, mzi, phase_shifter_fn=PS)\n", "\n", "## Note: you can use a MZI. Be careful to put the phase on the right, as the full layer of phase_shifter_fn is on the left of the circuit" ] }, { "cell_type": "code", "execution_count": 13, "id": "27eaed7eb54279e5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The error between the two unitaries is 9.693396401890419e-09\n" ] } ], "source": [ "## Exercise: check the norm of the difference to be sure it has worked well\n", "\n", "## Solution:\n", "print(\"The error between the two unitaries is\", np.linalg.norm(U - circuit_u.compute_unitary()))" ] }, { "cell_type": "markdown", "id": "6592d5435524e1c8", "metadata": {}, "source": [ "

\n", "Even if it is a good example to show how to decompose an arbitrary unitary matrix to a generic interferometer using Perceval, it is also possible to compute results without doing so. As the decomposition step is quite time-consuming, it's often better to skip this step when you're not sure if you require it.

" ] }, { "cell_type": "markdown", "id": "210491d7cc3bb832", "metadata": {}, "source": [ "### 4. Black Box\n", "\n", "To improve readability, the circuit can be constructed in different steps, combined with multiple hierarchy levels. The higher level circuit then treat their complex components as black boxes. Writing black boxes helps writing generic reusable operations.\n" ] }, { "cell_type": "code", "execution_count": 14, "id": "d054d0c2488bf8d8", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "BELL STATE PREPAR.\n", "\n", "\n", "\n", "UPPER MZI\n", "\n", "\n", "\n", "LOWER MZI\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pre_MZI = (pcvl.Circuit(4, name=\"Bell State Prepar.\")\n", " .add(0, BS())\n", " .add(2, BS())\n", " .add(1, PERM([1, 0])))\n", "\n", "upper_MZI = (pcvl.Circuit(2, name=\"upper MZI\")\n", " .add(0, PS(phi=pcvl.P('phi_0')))\n", " .add(0, BS())\n", " .add(0, PS(phi=pcvl.P('phi_2')))\n", " .add(0, BS()))\n", "\n", "lower_MZI = (pcvl.Circuit(2, name=\"lower MZI\")\n", " .add(0, PS(phi=pcvl.P('phi_1')))\n", " .add(0, BS())\n", " .add(0, PS(phi=pcvl.P('phi_3')))\n", " .add(0, BS()))\n", "\n", "chip = (pcvl.Circuit(4)\n", " .add(0, pre_MZI)\n", " .add(0, upper_MZI, merge=False)\n", " .add(2, lower_MZI, merge=False))\n", "\n", "pcvl.pdisplay(chip)" ] }, { "cell_type": "code", "execution_count": 15, "id": "8c9f4d442f6501df", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "BELL STATE PREPAR.\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "UPPER MZI\n", "\n", "\n", "Φ=phi_0\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "LOWER MZI\n", "\n", "\n", "Φ=phi_1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_3\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## You can still display the inside of black boxes with:\n", "pcvl.pdisplay(chip, recursive=True)" ] }, { "cell_type": "markdown", "id": "da8e743391f0fb41", "metadata": {}, "source": [ "## III. Experiments\n", "\n", "More than just defining a LO circuit, it could be interesting to build what is around the unitary components. Setuping the input state, noise, post-selection functions, detectors, etc. is important in real world computations. Great news: that's exactly what an `Experiment` is made for." ] }, { "cell_type": "code", "execution_count": 16, "id": "2c7a2e927be63ad3", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CPLX\n", "\n", "\n", "\n", "\n", "\n", "\n", "1\n", "\n", "\n", "0\n", "\n", "\n", "1\n", "\n", "\n", "0\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Define your experiment\n", "experiment = pcvl.Experiment(4, pcvl.NoiseModel(brightness= 0.8, indistinguishability=0.9))\n", "experiment.add(0, chip) # Those two lines could also be replaced by experiment = pcvl.Experiment(chip, NoiseModel(...))\n", "\n", "# Define your input\n", "experiment.with_input(pcvl.BasicState([1, 0, 1, 0]))\n", "\n", "# Define conditions on the output\n", "experiment.min_detected_photons_filter(2) # Postselection on the number of photons of the output\n", "experiment.set_postselection(pcvl.PostSelect(\"[0, 1] == 1 & [2, 3] == 1\")) # Postselection using logical conditions\n", "\n", "pcvl.pdisplay(experiment) # chip is now a black box for the experiment" ] }, { "cell_type": "code", "execution_count": 17, "id": "def7d64fdaca0dc2", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CPLX\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "" ], "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Experiments can be composed more freely than circuits\n", "expe = pcvl.Experiment(6)\n", "expe.add({0: 1, 1: 3, 2: 2, 4: 0}, experiment) # Only the inner components are added\n", "\n", "pcvl.pdisplay(expe)" ] }, { "cell_type": "code", "execution_count": 18, "id": "169746f165657793", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'phi': Parameter(name='phi', value=None, min_v=0.0, max_v=6.283185307179586)}\n", "[[-1.000000e+00+6.123234e-17j -6.123234e-17+0.000000e+00j]\n", " [-6.123234e-17+0.000000e+00j 1.000000e+00-6.123234e-17j]]\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Variables are still accessible through experiment\n", "experiment = pcvl.Experiment(2).add(0, pcvl.BS()).add(0, pcvl.PS(pcvl.P(\"phi\"))).add(0, pcvl.BS())\n", "\n", "print(experiment.get_circuit_parameters())\n", "experiment.get_circuit_parameters()[\"phi\"].set_value(np.pi)\n", "\n", "# In case the Experiment describes a unitary circuit, this circuit can be retrieved using\n", "circ = experiment.unitary_circuit()\n", "\n", "print(circ.compute_unitary())\n", "\n", "pcvl.pdisplay(circ)" ] }, { "cell_type": "markdown", "id": "40f730b57e2df667", "metadata": {}, "source": [ "Experiments can also handle a few non-unitary components, which is not possible in the `Circuit` class. A tutorial part for advanced users covers this use case." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Computation_Tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "345cdcd4fc84356", "metadata": {}, "source": [ "# Local Computing" ] }, { "cell_type": "markdown", "id": "6ba3240f621f8ae4", "metadata": {}, "source": [ "This tutorial shows how to simulate a quantum experiment with Perceval running locally, on the user computer.\n", "\n", "Up to this point, we have focused on creating circuits.\n", "It's time to learn how to sample from them or describe their output distribution, from one or many different input states." ] }, { "cell_type": "markdown", "id": "5f1aced257345540", "metadata": {}, "source": [ "## I. Different layers of simulation\n", "\n", "Perceval computation classes are organised in three different layers, each having a specific role and set of features:\n", "\n", "A **Processor** relies on a **Simulator** which uses a **Backend** in order to compute results.\n", "\n", "Let's describe the intent behind each layer.\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
Layer descriptionLocal simulationsRemote computations
\n", "The **processor** layer is the high level interface designed as Perceval entry point to describe and run linear optics experiments.\n", "It supports the optical hardware capabilities available in actual single photons-based QPU. As such, only sampling and measurements are available\n", "and not probability amplitudes or state evolutions.\n", "\n", "Processors are meant to be used by *quantum algorithm classes* that would use them once or multiple times to achieve a goal. The simplest use\n", "is to perform sampling in the **Sampler** class.
\n", "This layer is designed so that a user doesn't have to change all their code to swap from a local to a remote computation.\n", "
\n", "**Processor** is the go-to class for a simulation directly on the user machine. It can simulate any unitary circuit, a small set of non-unitary components\n", "(TimeDelay, LossChannel) and feed-forward. It simulates real life noise.\n", "\n", "It is aimed at being usable by a Perceval *beginner*, up to an expert.\n", "\n", "**RemoteProcessor** uses the same syntax to create linear optics experiments. It then connects to a Quandela compatible Cloud provider.\n", "\n", "It targets a computation *platform* which can be a remote simulator or an actual QPU. Available features are defined on a per-platform basis (e.g. a remote\n", "simulator based on a strong simulation algorithm can output exact probabilities, whereas a QPU can only acquire samples).\n", "
\n", "On the receiving end of the experiment, a system has to compute results in order to send them back to the user. The **computation** layer is very diverse as it\n", "can be a raw \"perfect\" back-end, the implementation of an algorithm simulating a real life setup, or even an actual QPU.\n", "\n", "**Simulator classes** are the Swiss army knife of the local simulation algorithms. They are the most versatile, and as such, the harder to use. They target *advanced*\n", "users.\n", "\n", "They can simulate a large variety of noise, any type of input (noisy, superposed, mixed, etc.) and can compute a sampling as well as a state evolution (using strong simulation).\n", "\n", "They are built as an onion, where different **Simulator** layers can handle a particular non-unitary problem so that it can be extended by *expert* developers writing\n", "their own simulator class.\n", "\n", "Cloud **platforms** (simulator or actual QPU) embody this layer to produce results.\n", "
\n", "The back-end is the low level method to compute data.\n", "\n", "Locally, **Backend classes** are state-of-the-art implementations of published algorithms able to simulate perfect results,\n", "from a perfect input state in a purely unitary linear optics circuit.\n", "\n", "These are optimised algorithms, which can definitely be used by a Perceval *beginner* as long as they only require perfect simulations.\n", "\n", "Cloud QPU use their hardware to acquire data.\n", "\n", "Cloud simulators rely on algorithms that are specifically optimized for a given classical hardware (multithreaded, GPU, etc.).\n", "
\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "1305dd0510f1fecb", "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "from perceval import BS, BasicState, Circuit, Processor\n", "from perceval.algorithm import Sampler" ] }, { "cell_type": "markdown", "id": "3846837bc2c71ee2", "metadata": {}, "source": [ "## II. Computing probabilities\n", "\n", "For this part, we will take the [Hong-Ou-Mandel](https://en.wikipedia.org/wiki/Hong%E2%80%93Ou%E2%80%93Mandel_effect) experience as an example.\n", "\n", "It's one of the simplest experiments and yet it is very useful.\n", "\n", "Making two indistinguishable photons, one in each mode, enter one balanced beamsplitter $BS=\\frac{1}{\\sqrt{2}} \\left[\\begin{matrix}1 & 1\\\\1& -1\\end{matrix}\\right]$, we expect the outcome to be:\n", "\n", "$$|1,1\\rangle \\mapsto \\frac{|2,0\\rangle - |0,2\\rangle}{\\sqrt{2}} $$\n", "\n", "We will show how to verify this in the next steps using the Naive backend to recover the full probability distribution." ] }, { "cell_type": "code", "execution_count": 2, "id": "initial_id", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}\\frac{\\sqrt{2}}{2} & \\frac{\\sqrt{2}}{2}\\\\\\frac{\\sqrt{2}}{2} & - \\frac{\\sqrt{2}}{2}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Such a circuit can be created as follows\n", "circuit = BS.H()\n", "pcvl.pdisplay(circuit.compute_unitary())" ] }, { "cell_type": "code", "execution_count": 3, "id": "7febc504ba84b0d7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Amplitude of |1,1> giving |2,0> through H = (0.7071067811865477+0j)\n", "Amplitude of |1,1> giving |0,2> through H = (-0.7071067811865477+0j)\n", "Probability of |1,1> giving |0,2> through H = 0.5000000000000002\n" ] } ], "source": [ "# Let's compute the amplitudes in a perfect simulation (no noise) allowing us to use the low level SLOS back-end\n", "backend = pcvl.BackendFactory.get_backend(\"SLOS\")\n", "backend.set_circuit(circuit)\n", "backend.set_input_state(BasicState([1, 1])) # Only FockStates are accepted here\n", "print(\"Amplitude of |1,1> giving |2,0> through H =\", backend.prob_amplitude(BasicState([2, 0]))) # note that it's the amplitude !\n", "print(\"Amplitude of |1,1> giving |0,2> through H =\", backend.prob_amplitude(BasicState([0, 2])))\n", "print(\"Probability of |1,1> giving |0,2> through H =\", backend.probability(BasicState([0, 2])))" ] }, { "cell_type": "markdown", "id": "7465458dc2e8d4dd", "metadata": {}, "source": [ "The same simulation can also be achieved through a `Processor` using the `Sampler` algorithm to compute a table of probabilities\n", "A Processor consists of a simulation backend and a circuit (or an experiment). Even though the `Processor` is able to simulate noise, the default is to run\n", "perfect simulations." ] }, { "cell_type": "code", "execution_count": 4, "id": "7cb540128520e808", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|2,0> 1/2
|0,2> 1/2
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "p = Processor(\"SLOS\", circuit)\n", "p.with_input(BasicState([1, 1]))\n", "sampler = Sampler(p)\n", "# The result of an algorithm, such as the Sampler, is a Python dictionary containing at least a \"results\" key.\n", "# Other fields will be explained later in the tutorial.\n", "pcvl.pdisplay(sampler.probs()[\"results\"])" ] }, { "cell_type": "code", "execution_count": 5, "id": "5b27c78ea7bc5f64", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|2,0,0> 0.466531
|0,2,0> 0.455058
|0,1,1> 0.045284
|1,0,1> 0.031179
|1,1,0> 0.001716
|0,0,2> 0.000232062
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "## Exercise: Create a random 3x3 unitary, and output the table probabilities when the input |1,1,0> passes through the LO-Circuit it represents.\n", "\n", "## Solution:\n", "random_unitary = pcvl.Unitary(pcvl.Matrix.random_unitary(3))\n", "input_state = BasicState([1, 1, 0])\n", "\n", "p = Processor(\"SLOS\", random_unitary)\n", "p.with_input(input_state)\n", "sampler = Sampler(p)\n", "pcvl.pdisplay(sampler.probs()[\"results\"])" ] }, { "cell_type": "markdown", "id": "5fd7e9c4ee649485", "metadata": {}, "source": [ "## III. Sampling\n", "\n", "Although it's sometimes crucial to compute the output distribution, it's not what we can expect from a photonic chip. Indeed, realistically, we only can obtain a single sample from the distribution each time we run the circuit. From any strong simulation result, sampling data can be extrapolated. However, specialised sampling algorithms, such as Clifford & Clifford 2017, are more optimised computing samples on bigger systems rather than \"solving the whole quantum system\" as strong simulation would.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "cbc17e6567dc41cb", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
states
|0,2>
|0,2>
|0,2>
|0,2>
|2,0>
|0,2>
|0,2>
|2,0>
|2,0>
|2,0>
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# As before, the low level back-end can be used directly\n", "sampling_backend = pcvl.Clifford2017Backend()\n", "sampling_backend.set_circuit(circuit)\n", "sampling_backend.set_input_state(BasicState([1, 1]))\n", "pcvl.pdisplay(sampling_backend.samples(10))" ] }, { "cell_type": "code", "execution_count": 7, "id": "354a78972a0a92c", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state count
|0,2> 511
|2,0> 489
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "p = Processor(\"CliffordClifford2017\", circuit)\n", "p.min_detected_photons_filter(0) # Do not filter out any output state\n", "# A Processor input can be FockState, NoisyFockState, LogicalState (if ports are defined), StateVector, or SVDistribution\n", "p.with_input(pcvl.BasicState([1, 1]))\n", "\n", "# The sampler holds 'probs', 'sample_count' and 'samples' calls. You can use the one that fits your needs!\n", "sampler = Sampler(p)\n", "\n", "# A sampler call will return a Python dictionary containing sampling results, and a performance score\n", "sample_count = sampler.sample_count(1000)\n", "# sample_count contains {'results': , 'global_perf': float [0.0 - 1.0]}\n", "pcvl.pdisplay(sample_count['results'])\n", "# Only FockStates (they are the result of a measure on detectors)" ] }, { "cell_type": "code", "execution_count": 8, "id": "b41cf47239178fcc", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state count
|0,2,0> 468
|2,0,0> 449
|0,1,1> 57
|1,0,1> 25
|1,1,0> 1
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "## Exercise: Implement the code to compute 1000 samples from the 3x3 Unitary we used earlier\n", "\n", "#Solution:\n", "p = pcvl.Processor(\"CliffordClifford2017\", random_unitary)\n", "p.min_detected_photons_filter(0) # Do not filter out any output state\n", "p.with_input(pcvl.BasicState([1, 1, 0]))\n", "\n", "sampler = pcvl.algorithm.Sampler(p)\n", "sample_count = sampler.sample_count(1000)\n", "pcvl.pdisplay(sample_count['results'])\n", "\n", "## Question: How many states do we have for 3 modes and 2 photons?\n", "## Answer: There are 6 different states\n", "\n", "\n", "## Question: How many states do we have for m modes and n photons?\n", "## Answer: There are m+n-1 choose n different states (cf Bar and Star problems)." ] }, { "cell_type": "markdown", "id": "9e646b8343e3005a", "metadata": {}, "source": [ "Note : to approximate with decent precision a distribution over $M$ different states, we would need $M^2$ samples. This can be shown by [Hoeffding's inequality](https://en.wikipedia.org/wiki/Hoeffding%27s_inequality)." ] }, { "cell_type": "markdown", "id": "b111e68673d71981", "metadata": {}, "source": [ "## IV. Performance and output state filtering\n", "\n", "Perceval Processors have a built-in way of computing performance scores.\n", "\n", "There are three different performance scores:\n", "* Global performance\n", "* Physical performance\n", "* Logical performance\n", "\n", "These performance scores help measure the real duration of a data acquisition on a real QPU.\n", "\n", "The global performance, which is the product of both physical and logical performances, is always returned.\n", "The physical and logical performances can be returned by setting:\n", "> proc.compute_physical_logical_perf(True)\n", "\n", "Leaving this parameter as default can speed up the computation is some situations.\n", "\n", "#### a. Physical performance\n", "\n", "This score is related to the number of detections (on a QPU: number of clicks). It drops output states where photons have been lost, or finish in the same mode.\n", "\n", "For instance, an imperfect source makes this score drop.\n", "\n", "However, you can choose not to filter any output state by lowering the expected clicks with:\n", "> proc.min_detected_photons_filter(0)\n", "\n", "##### Processor.min_detected_photons_filter method\n", "\n", "Perceval aims at being an interface for the QPU and as such, proc.min_detected_photons_filter(int k) post selects on having at least k photons detected (for threshold detection: photons on at least k different modes). By default, this value is set to n where n is the expected number of input photons. This is useful for retrieving a logical interpretation, making sure that no photon has been lost due to noise and coherent with the use of threshold detectors. However, for various applications (for instance machine learning where we use the full Fock space and resolve the number of photons), you will have to set it to 0 (and you may introduce you own post selection scheme if needed)." ] }, { "cell_type": "code", "execution_count": 9, "id": "5e5161b2728d6fa8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Physical perf of perfect processor = 1.0\n", "Physical perf of imperfect processor = 0.09\n", "Physical perf of imperfect processor (without selection) = 0.9999999999999999\n" ] } ], "source": [ "# Create an empty circuit (each input mode is directly connected to a detector without interacting with any other)\n", "empty_circuit = Circuit(4)\n", "\n", "perfect_proc = Processor(\"SLOS\", empty_circuit)\n", "imperfect_proc = Processor(\"SLOS\", empty_circuit, noise=pcvl.NoiseModel(brightness=0.3))\n", "\n", "# We need to specify how many photons we want\n", "perfect_proc.min_detected_photons_filter(2)\n", "imperfect_proc.min_detected_photons_filter(2)\n", "\n", "# Set the same input in both processors\n", "input_state = BasicState([1, 0, 1, 0])\n", "perfect_proc.with_input(input_state)\n", "imperfect_proc.with_input(input_state)\n", "\n", "# Enable the computation of physical performance\n", "perfect_proc.compute_physical_logical_perf(True)\n", "imperfect_proc.compute_physical_logical_perf(True)\n", "\n", "perfect_sampler = Sampler(perfect_proc)\n", "perfect_probs = perfect_sampler.probs()\n", "imperfect_sampler = Sampler(imperfect_proc)\n", "imperfect_probs = imperfect_sampler.probs()\n", "\n", "print('Physical perf of perfect processor =', perfect_probs['physical_perf'])\n", "print('Physical perf of imperfect processor =', imperfect_probs['physical_perf']) # source emission probability**2\n", "\n", "# You can still disable output state filtering\n", "imperfect_proc.min_detected_photons_filter(0)\n", "imperfect_probs = imperfect_sampler.probs()\n", "print('Physical perf of imperfect processor (without selection) =', imperfect_probs['physical_perf'])" ] }, { "cell_type": "markdown", "id": "37976ce3a22f13c1", "metadata": {}, "source": [ "#### b. Logical performance\n", "\n", "This performance computation is set up by heralded modes and/or post-selection function set in a processor.\n", "\n", "Depending on the circuit used, on the post-selection function, you may observe that physical and logical performance score interact. So, if you're interested on a theoretical gate performance, you should disable physical post-selection with:\n", "> proc.min_detected_photons_filter(- sum(proc.heralds.values()))\n", "\n", "Here is a quick example of the heralding / post-selection syntax in Perceval. You will see the result later on in this notebook." ] }, { "cell_type": "code", "execution_count": 10, "id": "9ad20a20b967c7f4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "With herald only\n", "Logical perf = 0.7500000000000003\n", "{\n", "\t|1,0>: 0.02859547920896832\n", "\t|0,1>: 0.9714045207910317\n", "}\n", "With herald + post-selection function\n", "Logical perf = 0.7285533905932741\n", "{\n", "\t|0,1>: 1\n", "}\n" ] } ], "source": [ "circuit = Circuit(3) // BS() // (1, BS()) // BS()\n", "p = Processor(\"SLOS\", circuit)\n", "p.add_herald(2, 0) # Third mode is heralded (0 photon in, 0 photon expected out)\n", "\n", "p.min_detected_photons_filter(1) # Only non-heralded modes must be taken into account in this filter\n", "\n", "# Enable the computation of logical performance\n", "p.compute_physical_logical_perf(True)\n", "\n", "# After a mode is heralded, you must not take it into account when setting an input to the processor\n", "p.with_input(pcvl.BasicState([1, 0]))\n", "sampler = Sampler(p)\n", "probs = sampler.probs()\n", "print(\"With herald only\")\n", "print(\"Logical perf =\", probs['logical_perf'])\n", "print(probs['results'])\n", "\n", "# A post-selection function can be created like this:\n", "postselect_func = pcvl.PostSelect(\"[1] == 1\") # meaning we required 1 photon detection in mode #1\n", "\n", "p.set_postselection(postselect_func) # Add post-selection\n", "probs = sampler.probs()\n", "print(\"With herald + post-selection function\")\n", "print(\"Logical perf =\", probs['logical_perf'])\n", "print(probs['results'])" ] }, { "cell_type": "markdown", "id": "994f2f5f5bfd9479", "metadata": {}, "source": [ "Now that you know the basics of local computing, let's see how to execute jobs remotely using Perceval." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Density_matrix_Fock_space.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "# Density matrices in Fock space" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "In this notebook we introduce our new feature: __density matrices__ in __fock space__. This space is a mathematical framework to describe the quantum states of a system with variable number of particles, __photons__ in our case. Note that this space is much larger than the computational/logical space. Fock space is native to _Perceval_ and hence these matrices can be indispensable for linear optics computations.\n", "\n", "The difference in the basis is demonstrated in the following example of 1-qubit X gate. In logical space, the basic states of the system will be $|0\\rangle$, and $|1\\rangle$. The linear optics circuit implementation for this gate consists of a 2 mode circuit with the following basic states in the fock space - $|00\\rangle$, $|10\\rangle$, $|01\\rangle$, and, $|11\\rangle$.\n", "\n", "We will demonstrate how to create density matrices in _Perceval_ and the different methods can be applied on them for computation using @ simple examples - Bell State generation and Hong-Ou_Mandel effect.\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "import numpy as np\n", "from perceval.components import BS, Source, Circuit, catalog\n", "from perceval.utils import BasicState, DensityMatrix\n", "from perceval import Simulator\n", "from perceval.backends import SLOSBackend" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## I. Generating and Evolving a Bell State density matrix" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ " In _Perceval_, a density matrix is created by simply converting from the corresponding BasicState or StateVector Distribution." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "image/png": "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AkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAk+FkG94IIL4vrrr+/rwwCAD+2AgnrbbbfFOeecE/X19fGZz3wmpk+fHi+88MKhOrYD0tbWFuPGjevrwwDgU+qAgvr444/H7Nmzo729PR555JF4++2344tf/GLs3LnzUB0fAHwylIPwyiuvlIgojz/++F63aWlpKdOmTSttbW3l2GOPLfX19eXaa68tu3fvrm7T1NRUrrvuujJ37twyZMiQMnz48NLa2trjfTZs2FCmTp1ajj766FJfX19mzJhRNm/eXEopZf78+SUiesz8+fP3u66UUlpbW0tjY2O56667yujRo0tDQ0Npbm4u27Ztq27z29/+tpx++ull4MCBZejQoWXy5Mllx44dH/j1vvnmm6Wrq6s6HR0d7zs2Y4wxn7zp6uraZxMPKqgvvvhiiYjy7LPP7nWblpaWUldXV5qbm8tzzz1XHnjggTJs2LByyy23VLdpamoqDQ0Npa2traxbt64sXLiwVCqVsmTJklJKKd3d3WXcuHFl4sSJZeXKlaW9vb2cddZZpampqZRSyq5du8qNN95YTjvttNLZ2Vk6OzvLrl279ruulHeDWldXVy6//PLy7LPPlmXLlpXjjjuuenybNm0qRxxxRPnJT35S1q9fX5555pkyb968sn379g/8eltbW/v8P7oxxpj8OWRB7e7uLl/60pfK+eefv8/tWlpaytChQ8vOnTurz91xxx2lrq6udHd3l1LeDerEiRN7rDvnnHPKTTfdVEopZcmSJaWmpqZs3Lix+vrzzz9fIqKsWLGilPLfM8336u262traHmekc+fOLePHjy+llPK3v/2tRET5xz/+0avvizNUY4w5PGd/Qf3Qn/KdPXt2PPfcc/Gb3/xmv9s2NjZGbW1t9fGECRNix44d0dHRUX3uzDPP7LFmxIgR8corr0RExNq1a2PUqFExatSo6utjx46NwYMHx9q1a/e6396uO/HEE6O+vv4D993Y2BiTJ0+OM844I2bMmBF33nlnvP7663vd54ABA6KhoaHHAHD4+1BBnTNnTjzwwAOxdOnSOOGEE1IO5Mgjj+zxuFKpxJ49e1Le+2D2XVNTE4888kg89NBDMXbs2Pj5z38ep556aqxfv/4jOTYAPhkOKKillJgzZ04sXrw4/vSnP8XnPve5Xq1bvXp1vPHGG9XH7e3tUVdX1+PMcV/GjBkTHR0dPc5o16xZE1u3bo2xY8dGRET//v2ju7v7gNf1RqVSifPPPz9+8IMfxNNPPx39+/ePxYsX93o9AIe/Awrq7Nmz49e//nXcfffdUV9fH5s3b47Nmzf3iOUHeeutt+Kaa66JNWvWxIMPPhitra0xZ86c6Nevd7ufMmVKnHHGGXHVVVfFU089FStWrIiZM2dGU1NTnH322RHx7mXb9evXx6pVq2LLli2xe/fuXq3bnyeffDJ+9KMfxcqVK2Pjxo3xu9/9Lv7973/HmDFjerUegE+HAwrqHXfcEV1dXXHBBRfEiBEjqrNo0aJ9rps8eXKcfPLJMWnSpGhubo6pU6dGW1tbr/dbqVTi/vvvjyFDhsSkSZNiypQpcdJJJ/XY7xVXXBEXX3xxXHjhhTFs2LC45557erVufxoaGmLZsmVx6aWXximnnBLf//7348c//nFccsklvX4PAA5/lVJKOZQ7mDVrVmzdujXuu+++Q7mbj61t27bFoEGD+vowADhIXV1d+/yg6cfiXr4A8EknqACQ4JBf8v20c8kX4PDgki8AfAQEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAmO6OsDgE+0h+4/8DWXTMs/DqDPOUMFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACRwc3w4GG50D/w/Z6gAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCeoiVUvr6EABIsL+f54J6iG3fvr2vDwGABPv7eV4pTqEOqT179sSmTZuivr4+KpVKXx8OAAeolBLbt2+PkSNHRr9+ez8PFVQASOCSLwAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACf4PTErdrfwp/p0AAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "input_dm = DensityMatrix.from_svd(BasicState([1,0,1,0,0,0]))\n", "pcvl.pdisplay(input_dm)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "The next step in applying the evolution operator is to define the LO circuit corresponding to the operation. Here, it is demonstrated by a linear optics circuit consisting of a Hadamard gate and a post-processed CNOT gate used for Bell State generation." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "bell_circ = Circuit(m=6)\n", "\n", "bell_circ.add(0, BS.H())\n", "bell_circ.add(0, catalog['postprocessed cnot'].build_circuit(), merge=True)\n", "pcvl.pdisplay(bell_circ)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "To perform the computation on the input density matrix, a simulator is constructed with the circuit and the necessary post-selection function." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Output Density matrix - Bell State\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAdQAAAGFCAYAAABE9QI+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAU5ElEQVR4nO3dbWyV9f348U+pAta23ChDUH44FzXgTVm8ISihGsgUF4HpsA/cKMZsbgM3oyNGs9guWeYTt2xZmFl8ALhFh0uGbkYdxqFsk0qYgjcQ0YVBN4oOlZYbRS3f/wP/O7ObQJEPVuvrlXwfnOtc33N9ezR957p6uE5VKaUEAHBYBvT1AgCgPxBUAEggqACQQFABIIGgAkACQQWABIIKAAmO6usF9Hf79u2LrVu3Rl1dXVRVVfX1cgA4RKWU2LlzZ4wePToGDNj/eaigHmFbt26NMWPG9PUyADhM7e3tcdJJJ+33eZd8j7C6urq+XgIACQ72+/xjH9SLLroobrjhhr5exofmMi9A/3Cw3+epQV24cGGcfPLJMXjw4Jg4cWKsXr068+U/tNbW1pgwYUJfLwOAfiwtqEuXLo0bb7wxWlpa4umnn46Ghoa45JJL4tVXX806BAB8fJUk559/fpk3b17lcXd3dxk9enS5/fbb9zunubm5zJw5s7S2tpbjjz++1NXVleuuu67s3bu3sk9jY2O5/vrry4IFC8qwYcPKyJEjS0tLS4/X2bx5c5kxY0Y59thjS11dXZk9e3bZtm1bKaWURYsWlYjoMRYtWnTQeaWU0tLSUhoaGsrdd99dxo4dW+rr60tTU1Pp6urq9fvS2dn5P8c3DMMwPnmjs7PzgL/vU4K6d+/eUl1dXZYtW9Zj+5w5c8qMGTP2O6+5ubnU1taWpqam8vzzz5cHH3ywjBgxotx6662VfRobG0t9fX1pbW0tGzduLEuWLClVVVVl+fLlpZT3wj1hwoQyefLksmbNmtLW1lbOOeec0tjYWEopZc+ePeWmm24qZ5xxRuno6CgdHR1lz549B51XyntBra2tLVdccUV57rnnysqVK8sJJ5zQY33/7a233iqdnZ2V0d7e3uf/ExiGYRiHPz6SoP7zn/8sEVGefPLJHtsXLFhQzj///P3Oa25uLsOHDy+7d++ubLvzzjtLbW1t6e7uLqW8F9TJkyf3mHfeeeeVm2++uZRSyvLly0t1dXXZsmVL5fkXXnihRERZvXp1KeU/Z5rv19t5NTU1Pc5IFyxYUCZOnLjfn6mlpaXP/6MbhmEY+eNgQe3zT/k2NDRETU1N5fGkSZNi165d0d7eXtl29tln95gzatSoyt9mN2zYEGPGjOnxbz3Hjx8fQ4cOjQ0bNuz3uL2dd/LJJ/f4qPT7j/1Bbrnllujs7KyM9/8cAPRfKTd2OP7446O6ujpeeeWVHttfeeWVOOGEEw779Y8++ugej6uqqmLfvn2H/bpH4tiDBg2KQYMGHellAfAxk3KGOnDgwDjnnHPiscceq2zbt29fPPbYYzFp0qQDzl23bl28+eablcdtbW1RW1vb67sLjRs3Ltrb23ucCa5fvz527NgR48ePr6yvu7v7kOcBQG+lXfK98cYb46677oolS5bEhg0b4pvf/Gbs3r07rrnmmgPOe/vtt+Paa6+N9evXx0MPPRQtLS0xf/78A94v8f2mTZsWZ511Vlx99dXx9NNPx+rVq2POnDnR2NgY5557bkS8d9l206ZNsXbt2ti+fXvs3bu3V/MAoLfSgtrU1BR33HFH3HbbbTFhwoRYu3ZtPPLIIzFy5MgDzps6dWqceuqpMWXKlGhqaooZM2ZEa2trr49bVVUVDzzwQAwbNiymTJkS06ZNi1NOOSWWLl1a2efKK6+MSy+9NC6++OIYMWJE3Hvvvb2aBwC9VVVKKX118Llz58aOHTvi/vvv76slHHFdXV0xZMiQvl4GAIeps7Mz6uvr9/t8n3/KFwD6g49FUOfOnRuzZs3q62UAwId2SEFduXJlXH755TF69Oioqqo67Eu1ixcvTrvcu3jx4hg6dGjKawHAoTqkoO7evTsaGhpi4cKFR2o9APCJdEhBnT59evzgBz+IL33pS72e8++vTvvFL34RY8aMiZqamrjqqquis7Pzf/a94447YtSoUXHcccfFvHnz4p133qk898Ybb8ScOXNi2LBhUVNTE9OnT4+XXnopIiIef/zxuOaaa6KzszOqqqqiqqqq8knhA82L+M+Z7R/+8IcYN25c1NbWxqWXXhodHR2VfR5//PE4//zz49hjj42hQ4fGhRdeGJs3bz6Utw6Afu4j+Rvqyy+/HPfdd1/8/ve/j0ceeSSeeeaZ+Na3vtVjnxUrVsTf/va3WLFiRSxZsiQWL14cixcvrjw/d+7cWLNmTfzud7+LVatWRSklLrvssnjnnXfiggsuiJ/85CdRX18fHR0d0dHREd/97ncPOu/f9uzZE3fccUf88pe/jJUrV8aWLVsq8999992YNWtWNDY2xrPPPhurVq2Kr3/96/v9otm9e/dGV1dXjwHAp8AB7/R7ABHxP98us7+bxVdXV5d//OMflW0PP/xwGTBgQOno6CilvHeT/LFjx5Z33323ss/s2bNLU1NTKaWUjRs3logof/nLXyrPb9++vRxzzDHlvvvuK6W89zVtQ4YM6XHs3s6LiPLyyy9X9lm4cGEZOXJkKaWU1157rUREefzxx3vztrg5vmEYRj8dH4ub4//f//1fnHjiiZXHkyZNin379sWLL75Y2XbGGWdEdXV15fF/3wD/qKOOiokTJ1aeP+644+L0008/6A3wezOvpqYmPve5z33gsYcPHx5z586NSy65JC6//PL46U9/2uNy8H9zc3yAT6ePxT+bifj43QC/vO9+F4sWLYpVq1bFBRdcEEuXLo3TTjst2traPvC1Bg0aFPX19T0GAP3fRxLULVu2xNatWyuP29raYsCAAXH66af3av64cePi3Xffjaeeeqqy7bXXXosXX3zxoDfAP9i83vr85z8ft9xySzz55JNx5plnxj333HNI8wHo3w4pqLt27Yq1a9fG2rVrIyIqN5zfsmXLAecNHjw4mpubY926dfGnP/0pvv3tb8dVV13V6692O/XUU2PmzJnxta99Lf785z/HunXr4itf+UqceOKJMXPmzIh47wb4u3btisceeyy2b98ee/bs6dW8g9m0aVPccsstsWrVqti8eXMsX748XnrppRg3blyv5gPwKdGrT9r8fytWrPjAP9Q2Nzcf8EM6DQ0N5ec//3kZPXp0GTx4cPnyl79cXn/99co+zc3NZebMmT3mfec73ymNjY2Vx6+//nr56le/WoYMGVKOOeaYcskll5SNGzf2mPONb3yjHHfccSUiSktLS6/mfdCHmZYtW1b+/dZs27atzJo1q4waNaoMHDiwjB07ttx2222lu7u7V+9ZZ2dnn/8h3TAMwzj8cbAPJR3xm+O3trbG/fffXzmr/bRxc3yA/sHN8QHgIyCoAJCgT78P9dPAJV+A/sElXwD4CAgqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEnwsgnrRRRfFDTfc0NfLAIAP7ZCCevvtt8d5550XdXV18ZnPfCZmzZoVL7744pFa2yFpbW2NCRMm9PUyAPiUOqSgPvHEEzFv3rxoa2uLRx99NN555534whe+ELt37z5S6wOAT4ZyGF599dUSEeWJJ57Y7z7Nzc1l5syZpbW1tRx//PGlrq6uXHfddWXv3r2VfRobG8v1119fFixYUIYNG1ZGjhxZWlpaerzO5s2by4wZM8qxxx5b6urqyuzZs8u2bdtKKaUsWrSoRESPsWjRooPOK6WUlpaW0tDQUO6+++4yduzYUl9fX5qamkpXV1dln9/85jflzDPPLIMHDy7Dhw8vU6dOLbt27frAn/ett94qnZ2dldHe3v4/azMMwzA+eaOzs/OATTysoL700kslIspzzz23332am5tLbW1taWpqKs8//3x58MEHy4gRI8qtt95a2aexsbHU19eX1tbWsnHjxrJkyZJSVVVVli9fXkoppbu7u0yYMKFMnjy5rFmzprS1tZVzzjmnNDY2llJK2bNnT7npppvKGWecUTo6OkpHR0fZs2fPQeeV8l5Qa2tryxVXXFGee+65snLlynLCCSdU1rd169Zy1FFHlR//+Mdl06ZN5dlnny0LFy4sO3fu/MCft6Wlpc//oxuGYRj544gFtbu7u3zxi18sF1544QH3a25uLsOHDy+7d++ubLvzzjtLbW1t6e7uLqW8F9TJkyf3mHfeeeeVm2++uZRSyvLly0t1dXXZsmVL5fkXXnihRERZvXp1KeU/Z5rv19t5NTU1Pc5IFyxYUCZOnFhKKeWvf/1riYjy97//vVfvizNUwzCM/jkOFtQP/SnfefPmxfPPPx+//vWvD7pvQ0ND1NTUVB5PmjQpdu3aFe3t7ZVtZ599do85o0aNildffTUiIjZs2BBjxoyJMWPGVJ4fP358DB06NDZs2LDf4/Z23sknnxx1dXUfeOyGhoaYOnVqnHXWWTF79uy466674o033tjvMQcNGhT19fU9BgD934cK6vz58+PBBx+MFStWxEknnZSykKOPPrrH46qqqti3b1/Kax/Osaurq+PRRx+Nhx9+OMaPHx8/+9nP4vTTT49NmzZ9JGsD4JPhkIJaSon58+fHsmXL4o9//GN89rOf7dW8devWxZtvvll53NbWFrW1tT3OHA9k3Lhx0d7e3uOMdv369bFjx44YP358REQMHDgwuru7D3leb1RVVcWFF14Y3//+9+OZZ56JgQMHxrJly3o9H4D+75CCOm/evPjVr34V99xzT9TV1cW2bdti27ZtPWL5Qd5+++249tprY/369fHQQw9FS0tLzJ8/PwYM6N3hp02bFmeddVZcffXV8fTTT8fq1atjzpw50djYGOeee25EvHfZdtOmTbF27drYvn177N27t1fzDuapp56KH/7wh7FmzZrYsmVL/Pa3v41//etfMW7cuF7NB+DT4ZCCeuedd0ZnZ2dcdNFFMWrUqMpYunTpAedNnTo1Tj311JgyZUo0NTXFjBkzorW1tdfHraqqigceeCCGDRsWU6ZMiWnTpsUpp5zS47hXXnllXHrppXHxxRfHiBEj4t577+3VvIOpr6+PlStXxmWXXRannXZafO9734sf/ehHMX369F6/BgD9X1UppRzJA8ydOzd27NgR999//5E8zMdWV1dXDBkypK+XAcBh6uzsPOAHTT8W9/IFgE86QQWABEf8ku+nnUu+AP2DS74A8BEQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQ4qq8XAJ9oDz9w6HOmz8xfxyeR945+xhkqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASBBVSml9PUi+rOurq4YMmRIXy8DgMPU2dkZ9fX1+33eGSoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJDiqrxcAn2gPP3Doc6bPzF/HJ5H3jn7GGSoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIEFVKaX09SL6s66urhgyZEhfLwOAw9TZ2Rn19fX7fd4ZKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBPcJKKX29BAASHOz3uaAeYTt37uzrJQCQ4GC/z6uKU6gjat++fbF169aoq6uLqqqqvl4OAIeolBI7d+6M0aNHx4AB+z8PFVQASOCSLwAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACf4f+Xz3C3tCMEUAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "cnot = catalog['postprocessed cnot'].build_processor() # creating a postprocessed cnot from catalog for the heralding and the post-selection function\n", "\n", "bell_simulator = Simulator(SLOSBackend())\n", "bell_simulator.set_circuit(bell_circ)\n", "bell_simulator.set_selection(heralds=cnot.heralds, postselect=cnot.post_select_fn)\n", "\n", "# Applying the evolution\n", "bell_out_dm = bell_simulator.evolve_density_matrix(input_dm) # apply evolution on the density matrix\n", "print('Output Density matrix - Bell State')\n", "pcvl.pdisplay(bell_out_dm)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "This output density matrix corresponds identically to evolving a statevector through the circuit and then converting the output SVD into a density matrix." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The output statevector distribution: sqrt(2)/2*|1,0,1,0,0,0>+sqrt(2)/2*|0,1,0,1,0,0>\n", "The density matrix converted from the output SVD\n" ] }, { "data": { "image/png": "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d7+LVatWRSklLrvssnjnnXfiggsuiJ/85CdRX18fHR0d0dHREd/97ncPOu/f9uzZE3fccUf88pe/jJUrV8aWLVsq8999992YNWtWNDY2xrPPPhurVq2Kr3/96/v9otm9e/dGV1dXjwHAp8AB7/R7ABHxP98us7+bxVdXV5d//OMflW0PP/xwGTBgQOno6CilvHeT/LFjx5Z33323ss/s2bNLU1NTKaWUjRs3logof/nLXyrPb9++vRxzzDHlvvvuK6W89zVtQ4YM6XHs3s6LiPLyyy9X9lm4cGEZOXJkKaWU1157rUREefzxx3vztrg5vmEYRj8dH4ub4//f//1fnHjiiZXHkyZNin379sWLL75Y2XbGGWdEdXV15fF/3wD/qKOOiokTJ1aeP+644+L0008/6A3wezOvpqYmPve5z33gsYcPHx5z586NSy65JC6//PL46U9/2uNy8H9zc3yAT6ePxT+bifj43QC/vO9+F4sWLYpVq1bFBRdcEEuXLo3TTjst2traPvC1Bg0aFPX19T0GAP3fRxLULVu2xNatWyuP29raYsCAAXH66af3av64cePi3Xffjaeeeqqy7bXXXosXX3zxoDfAP9i83vr85z8ft9xySzz55JNx5plnxj333HNI8wHo3w4pqLt27Yq1a9fG2rVrIyIqN5zfsmXLAecNHjw4mpubY926dfGnP/0pvv3tb8dVV13V6692O/XUU2PmzJnxta99Lf785z/HunXr4itf+UqceOKJMXPmzIh47wb4u3btisceeyy2b98ee/bs6dW8g9m0aVPccsstsWrVqti8eXMsX748XnrppRg3blyv5gPwKdGrT9r8fytWrPjAP9Q2Nzcf8EM6DQ0N5ec//3kZPXp0GTx4cPnyl79cXn/99co+zc3NZebMmT3mfec73ymNjY2Vx6+//nr56le/WoYMGVKOOeaYcskll5SNGzf2mPONb3yjHHfccSUiSktLS6/mfdCHmZYtW1b+/dZs27atzJo1q4waNaoMHDiwjB07ttx2222lu7u7V+9ZZ2dnn/8h3TAMwzj8cbAPJR3xm+O3trbG/fffXzmr/bRxc3yA/sHN8QHgIyCoAJCgT78P9dPAJV+A/sElXwD4CAgqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEnwsgnrRRRfFDTfc0NfLAIAP7ZCCevvtt8d5550XdXV18ZnPfCZmzZoVL7744pFa2yFpbW2NCRMm9PUyAPiUOqSgPvHEEzFv3rxoa2uLRx99NN555534whe+ELt37z5S6wOAT4ZyGF599dUSEeWJJ57Y7z7Nzc1l5syZpbW1tRx//PGlrq6uXHfddWXv3r2VfRobG8v1119fFixYUIYNG1ZGjhxZWlpaerzO5s2by4wZM8qxxx5b6urqyuzZs8u2bdtKKaUsWrSoRESPsWjRooPOK6WUlpaW0tDQUO6+++4yduzYUl9fX5qamkpXV1dln9/85jflzDPPLIMHDy7Dhw8vU6dOLbt27frAn/ett94qnZ2dldHe3v4/azMMwzA+eaOzs/OATTysoL700kslIspzzz23332am5tLbW1taWpqKs8//3x58MEHy4gRI8qtt95a2aexsbHU19eX1tbWsnHjxrJkyZJSVVVVli9fXkoppbu7u0yYMKFMnjy5rFmzprS1tZVzzjmnNDY2llJK2bNnT7npppvKGWecUTo6OkpHR0fZs2fPQeeV8l5Qa2tryxVXXFGee+65snLlynLCCSdU1rd169Zy1FFHlR//+Mdl06ZN5dlnny0LFy4sO3fu/MCft6Wlpc//oxuGYRj544gFtbu7u3zxi18sF1544QH3a25uLsOHDy+7d++ubLvzzjtLbW1t6e7uLqW8F9TJkyf3mHfeeeeVm2++uZRSyvLly0t1dXXZsmVL5fkXXnihRERZvXp1KeU/Z5rv19t5NTU1Pc5IFyxYUCZOnFhKKeWvf/1riYjy97//vVfvizNUwzCM/jkOFtQP/SnfefPmxfPPPx+//vWvD7pvQ0ND1NTUVB5PmjQpdu3aFe3t7ZVtZ599do85o0aNildffTUiIjZs2BBjxoyJMWPGVJ4fP358DB06NDZs2LDf4/Z23sknnxx1dXUfeOyGhoaYOnVqnHXWWTF79uy466674o033tjvMQcNGhT19fU9BgD934cK6vz58+PBBx+MFStWxEknnZSykKOPPrrH46qqqti3b1/Kax/Osaurq+PRRx+Nhx9+OMaPHx8/+9nP4vTTT49NmzZ9JGsD4JPhkIJaSon58+fHsmXL4o9//GN89rOf7dW8devWxZtvvll53NbWFrW1tT3OHA9k3Lhx0d7e3uOMdv369bFjx44YP358REQMHDgwuru7D3leb1RVVcWFF14Y3//+9+OZZ56JgQMHxrJly3o9H4D+75CCOm/evPjVr34V99xzT9TV1cW2bdti27ZtPWL5Qd5+++249tprY/369fHQQw9FS0tLzJ8/PwYM6N3hp02bFmeddVZcffXV8fTTT8fq1atjzpw50djYGOeee25EvHfZdtOmTbF27drYvn177N27t1fzDuapp56KH/7wh7FmzZrYsmVL/Pa3v41//etfMW7cuF7NB+DT4ZCCeuedd0ZnZ2dcdNFFMWrUqMpYunTpAedNnTo1Tj311JgyZUo0NTXFjBkzorW1tdfHraqqigceeCCGDRsWU6ZMiWnTpsUpp5zS47hXXnllXHrppXHxxRfHiBEj4t577+3VvIOpr6+PlStXxmWXXRannXZafO9734sf/ehHMX369F6/BgD9X1UppRzJA8ydOzd27NgR999//5E8zMdWV1dXDBkypK+XAcBh6uzsPOAHTT8W9/IFgE86QQWABEf8ku+nnUu+AP2DS74A8BEQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQ4qq8XAJ9oDz9w6HOmz8xfxyeR945+xhkqACQQVABIIKgAkEBQASCBoAJAAkEFgASCCgAJBBUAEggqACQQVABIIKgAkEBQASBBVSml9PUi+rOurq4YMmRIXy8DgMPU2dkZ9fX1+33eGSoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJDiqrxcAn2gPP3Doc6bPzF/HJ5H3jn7GGSoAJBBUAEggqACQQFABIIGgAkACQQWABIIKAAkEFQASCCoAJBBUAEggqACQQFABIEFVKaX09SL6s66urhgyZEhfLwOAw9TZ2Rn19fX7fd4ZKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACQQVABIIKgAkEFQASCCoAJBAUAEggaACQAJBPcJKKX29BAASHOz3uaAeYTt37uzrJQCQ4GC/z6uKU6gjat++fbF169aoq6uLqqqqvl4OAIeolBI7d+6M0aNHx4AB+z8PFVQASOCSLwAkEFQASCCoAJBAUAEggaACQAJBBYAEggoACf4f+Xz3C3tCMEUAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "output_svd = bell_simulator.evolve(BasicState([1,0,1,0,0,0]))\n", "print('The output statevector distribution:', output_svd)\n", "\n", "print('The density matrix converted from the output SVD')\n", "pcvl.pdisplay(DensityMatrix.from_svd(output_svd))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## II. Investigating the Hong-Ou-Mandel effect using Density Matrix" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "For this we create a simulator with a 2 mode circuit consisting of a beam splitter. The output density matrix demonstrates quantum coherence through the non-zero off-diagonal coefficients." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "output density matrix without any loss\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAdQAAAGFCAYAAABE9QI+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAVwklEQVR4nO3ce5CVdf3A8c8BBF33wkXiogTZoAOIS+NtUIfVkVKcEUjTrTHZNaeyoHI0amgad7GLTWMzNWVO40ws2mg4jWAxajIbSCVIppDGjrcYl2JRUTnLxVB2v78//HVqS2CRLxxdX6+Z7x/PeZ7veb7n6Oyb5zlnt5BSSgEAHJJ+5V4AAPQFggoAGQgqAGQgqACQgaACQAaCCgAZCCoAZDCg3Avo67q7u2PLli1RVVUVhUKh3MsB4CCllGLHjh0xevTo6Ndv39ehgnqYbdmyJcaMGVPuZQBwiDZv3hwnnHDCPve75XuYVVVVlXsJAGRwoJ/ngnqYuc0L0Dcc6Oe5oAJABoIKABkIKgBkIKgAkIGgAkAGggoAGQgqAGQgqACQgaACQAaCCgAZCCoAZCCoAJCBoAJABoIKABkIKgBkIKgAkIGgAkAGggoAGQgqAGQgqACQgaACQAaCCgAZCCoAZPCuD+p5550X1113XbmXAQD7lTWot956a4wbNy6OPvroOOuss2LdunU5n/4da25ujilTppR7GQD0YdmCumTJkrj++uujqakpHn/88aitrY0LL7wwXnrppVynAIB3r5TJmWeemebOnVva7urqSqNHj04333zzPuc0NDSkWbNmpebm5nTcccelqqqq9PnPfz7t2bOndExdXV360pe+lObPn5+GDBmSRowYkZqamno8zwsvvJBmzpyZjj322FRVVZUuv/zytHXr1pRSSosWLUoR0WMsWrTogPNSSqmpqSnV1tamO+64I40dOzZVV1en+vr61NnZ2ev3pVgs/s/5DcMwjPfeKBaL+/15nyWoe/bsSf37909Lly7t8ficOXPSzJkz9zmvoaEhVVZWpvr6+vTUU0+l5cuXp+HDh6dvfOMbpWPq6upSdXV1am5uTs8880xavHhxKhQK6aGHHkopvRXuKVOmpHPPPTc99thjae3atem0005LdXV1KaWUdu/enW644YY0adKk1NHRkTo6OtLu3bsPOC+lt4JaWVmZLr300vTkk0+m1atXp5EjR/ZY33/75z//mYrFYmls3ry57P8TGIZhGIc+jkhQ//GPf6SISI888kiPx+fPn5/OPPPMfc5raGhIQ4cOTbt27So9dtttt6XKysrU1dWVUnorqOeee26PeWeccUb6+te/nlJK6aGHHkr9+/dP7e3tpf1//etfU0SkdevWpZT+faX5n3o7r6KioscV6fz589NZZ521z9fU1NRU9v/ohmEYRv5xoKCW/Vu+tbW1UVFRUdqeOnVq7Ny5MzZv3lx67NRTT+0xZ9SoUaXPZtva2mLMmDExZsyY0v6JEyfG4MGDo62tbZ/n7e28cePGRVVV1due++0sWLAgisViafzn6wCg7xqQ40mOO+646N+/f7z44os9Hn/xxRdj5MiRh/z8Rx11VI/tQqEQ3d3dh/y8h+PcgwYNikGDBh3uZQHwLpPlCnXgwIFx2mmnRWtra+mx7u7uaG1tjalTp+537oYNG+L1118vba9duzYqKyt7XDnuz4QJE2Lz5s09rgQ3btwY27dvj4kTJ5bW19XVddDzAKC3st3yvf766+P222+PxYsXR1tbW3zhC1+IXbt2xdVXX73feW+88UZcc801sXHjxrj//vujqakp5s2bF/369W5p06dPj8mTJ8eVV14Zjz/+eKxbty7mzJkTdXV1cfrpp0fEW7dtN23aFOvXr49t27bFnj17ejUPAHorW1Dr6+vjlltuiRtvvDGmTJkS69evjwcffDBGjBix33kXXHBBjB8/PqZNmxb19fUxc+bMaG5u7vV5C4VC3HfffTFkyJCYNm1aTJ8+PU488cRYsmRJ6ZjLLrssLrroojj//PNj+PDhcffdd/dqHgD0ViGllMp18sbGxti+fXssW7asXEs47Do7O6OmpqbcywDgEBWLxaiurt7n/rJ/yxcA+gJBBYAMynrL9/3ALV+AvsEtXwA4AgQVADIQVADIQFABIANBBYAMBBUAMhBUAMhAUAEgA0EFgAwEFQAyEFQAyEBQASADQQWADAQVADIQVADIQFABIANBBYAMBBUAMhBUAMhAUAEgA0EFgAwEFQAyEFQAyEBQASADQQWADAQVADIQVADIQFABIANBBYAMBBUAMhBUAMhAUAEgA0EFgAwEFQAyEFQAyEBQASADQQWADAQVADIQVADIQFABIANBBYAMBBUAMhBUAMhAUAEgA0EFgAwEFQAyEFQA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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "sim = Simulator(SLOSBackend())\n", "sim.set_circuit(BS.H())\n", "\n", "# generate input density matrix\n", "source = Source() # perfect source\n", "input_density_matrix = DensityMatrix.from_svd(source.generate_distribution(BasicState([1, 1])))\n", "\n", "# evolution of density matrix\n", "output_density_matrix = sim.evolve_density_matrix(input_density_matrix)\n", "print('output density matrix without any loss')\n", "pcvl.pdisplay(output_density_matrix)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Apply a Loss operator on density matrix\n", "\n", "The density matrices allow for application of a loss operator by defining the modes on which photons are lost and the probability of loss. The application of the loss operator and its effect are illustrated below using a simple 2 mode LO circuit with a beam splitter and a noisy source with emission probability of 0.6. Computationally, this is equivalent to a perfect source with a probability of 0.4 for loosing a photon at each input mode before the circuit.\n" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "output density matrix with loss\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAdQAAAGFCAYAAABE9QI+AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAV/ElEQVR4nO3cfWyV9f3/8ddBBFehBRwT1H7H3NBBBTTqDJp48xNv8A6mkbo4XZ3RuUH2NTr9hmQRijqc0WWLurmYaNFN57IoOqJuTVXcDXgPc7Eap0RYrDqntN6C0v7+MHaryp1+4Gh9PJKTeM65Pud6n2L67HVdp6309PT0BAD4WAZUewAA6A8EFQAKEFQAKEBQAaAAQQWAAgQVAAoQVAAoYGC1B+jvuru789xzz2Xo0KGpVCrVHgeAzdTT05NXX301O+20UwYMWP9xqKBuYc8991zq6+urPQYAH9OqVauyyy67rPd5Qd3Chg4d+u5/zDorGTy4usNsbZf/rNoTABTT+/18PQR1C+s9zTt48GcvqAD9yMYu2/lQEgAUIKgAUICgAkABggoABQgqABQgqABQgKACQAGCCgAFCCoAFCCoAFCAoAJAAYIKAAUIKgAUIKgAUICgAkABggoABQgqABQgqABQgKACQAGCCgAFCCoAFCCoAFCAoAJAAZ/4oB588ME5++yzqz0GAGxQ0aBeddVVGTNmTLbbbrvst99+eeCBB0q+/Ec2d+7c7LnnntUeA4B+rFhQb7755pxzzjmZM2dOHnnkkUyaNClHHHFEXnzxxVK7AIBPrGJB/clPfpIzzjgjp512WsaPH5+rr746NTU1ufbaa9e7pqmpKdOnT09zc3NGjhyZ2tranHXWWVm7dm2f7bq7u3P++ednxIgRGTVqVObOndvn+ZUrV2batGkZMmRIamtrM2PGjLzwwgtJkpaWljQ3N2f58uWpVCqpVCppaWnZ6LrkP0e2N9xwQ8aMGZO6urqcdNJJefXVV8t80QDoN4oEde3atXn44YczZcqU/7zwgAGZMmVKlixZssG1bW1taW9vz7333pubbropt9xyS5qbm/tss2DBgmy//fa5//77c+mll2bevHlpbW1N8m5sp02blpdffjmLFy9Oa2trnnnmmTQ2NiZJGhsbc+6556ahoSEdHR3p6OhIY2PjRte95+mnn87ChQuzaNGiLFq0KIsXL84ll1yy3vezZs2adHV19bkB0P8NLPEiL730UtatW5cdd9yxz+M77rhjnnjiiQ2uHTRoUK699trU1NSkoaEh8+bNy3nnnZcLL7wwAwa82/uJEydmzpw5SZKxY8fmyiuvTFtbWw477LC0tbXlsccey4oVK1JfX58kuf7669PQ0JAHH3ww++67b4YMGZKBAwdm1KhRvfttbW3d6Lrk3WC3tLRk6NChSZJTTjklbW1tufjiiz/0/cyfP/8DPxAA0P9V/VO+kyZNSk1NTe/9yZMn57XXXsuqVat6H5s4cWKfNaNHj+69Ntve3p76+vreKCbJ+PHjM2zYsLS3t693v5u6bsyYMb0xff++P8zs2bPT2dnZe/vv9wFA/1XkCPXzn/98ttlmmz7XH5PkhRde6HNU+FFtu+22fe5XKpV0d3d/7NfdEvsePHhwBg8evKXHAuATpsgR6qBBg7L33nunra2t97Hu7u60tbVl8uTJG1y7fPnyvPnmm733ly5dmiFDhvQ5ctyQcePGZdWqVX2OBB9//PGsXr0648eP751v3bp1m70OADZVsVO+55xzTq655posWLAg7e3t+e53v5vXX389p5122gbXrV27Nqeffnoef/zx3HHHHZkzZ05mzZrVe/10Y6ZMmZIJEybk5JNPziOPPJIHHnggp556ag466KDss88+Sd49bbtixYosW7YsL730UtasWbNJ6wBgUxULamNjYy677LJccMEF2XPPPbNs2bLcddddH/ig0vsdeuihGTt2bA488MA0NjbmuOOO+8CvxWxIpVLJbbfdluHDh+fAAw/MlClTsuuuu+bmm2/u3eaEE07IkUcemUMOOSQjR47MTTfdtEnrAGBTVXp6enqqtfOmpqasXr06CxcurNYIW1xXV1fq6uqSc/83+axdW/3RpdWeAKCYzs7O1NbWrvf5qn/KFwD6A0EFgAKK/NrMR/XenwAEgE87R6gAUICgAkABggoABQgqABQgqABQgKACQAGCCgAFCCoAFCCoAFCAoAJAAYIKAAUIKgAUIKgAUICgAkABggoABQgqABQgqABQgKACQAGCCgAFCCoAFCCoAFBApaenp6faQ/RnXV1dqaurq/YY1XHnbdWeYOubOq3aEwBbSGdnZ2pra9f7vCNUAChAUAGgAEEFgAIEFQAKEFQAKEBQAaAAQQWAAgQVAAoQVAAoQFABoABBBYACBBUAChBUAChAUAGgAEEFgAIEFQAKEFQAKEBQAaAAQQWAAgQVAAoQVAAoQFABoABBBYACBBUAChBUACjgExHUpqamTJ8+vdpjAMBHtllBve+++3Lsscdmp512SqVSycKFC7fQWJuvpaUlw4YNq/YYAHxGbVZQX3/99UyaNClXXXXVlpoHAD6VNiuoU6dOzUUXXZSvf/3rm7xm7ty52XPPPfPLX/4y9fX1qampyYwZM9LZ2fmBbS+77LKMHj06O+ywQ2bOnJm3336797lXXnklp556aoYPH56amppMnTo1Tz31VJLk3nvvzWmnnZbOzs5UKpVUKpXMnTt3o+uS/xzZ/uEPf8i4ceMyZMiQHHnkkeno6Ojd5t57783Xvva1bL/99hk2bFgOOOCAPPvss5vzpQOgn9sq11D/8Y9/5Le//W1+//vf56677sqjjz6a733ve322ueeee/L000/nnnvuyYIFC9LS0pKWlpbe55uamvLQQw/l9ttvz5IlS9LT05Ojjjoqb7/9dvbff//89Kc/TW1tbTo6OtLR0ZEf/OAHG133njfeeCOXXXZZbrjhhtx3331ZuXJl7/p33nkn06dPz0EHHZS//e1vWbJkSc4888xUKpUPfa9r1qxJV1dXnxsA/d/ArbGTt956K9dff3123nnnJMkVV1yRo48+OpdffnlGjRqVJBk+fHiuvPLKbLPNNvnqV7+ao48+Om1tbTnjjDPy1FNP5fbbb89f/vKX7L///kmSX//616mvr8/ChQtz4oknpq6uLpVKpff1kmzSuiR5++23c/XVV+fLX/5ykmTWrFmZN29ekqSrqyudnZ055phjep8fN27cet/r/Pnz09zcXPLLB8CnwFY5Qv2f//mf3pgmyeTJk9Pd3Z0nn3yy97GGhoZss802vfdHjx6dF198MUnS3t6egQMHZr/99ut9focddsjuu++e9vb29e53U9fV1NT0xvL9+x4xYkSamppyxBFH5Nhjj83PfvazPqeD32/27Nnp7Ozsva1atWqDXxsA+odPxK/NJMm2227b536lUkl3d3fV9t3T09N7/7rrrsuSJUuy//775+abb85uu+2WpUuXfuhrDR48OLW1tX1uAPR/WyWoK1euzHPPPdd7f+nSpRkwYEB23333TVo/bty4vPPOO7n//vt7H/v3v/+dJ598MuPHj0+SDBo0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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "input_density_matrix.apply_loss([0, 1], 0.4) # this loss operator is equivalent to 0.6 emission probability from source\n", "\n", "lossy_output_density_matrix = sim.evolve_density_matrix(input_density_matrix) # evolving this lossy density matrix\n", "print('output density matrix with loss')\n", "pcvl.pdisplay(lossy_output_density_matrix)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "### Computing the expectation value of an operator" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "The expectation value of an operator $\\hat{O}$ over a quantum system represented by a density matrix $\\rho$ is given by the nice formula :\n", "\n", "$$ \\langle\\hat{O}\\rangle = Tr (\\hat{O}\\rho) $$\n", "\n", "In the next cell, we evaluate the expectation value of the number operator $\\hat{N}$ using the previous lossy density matrix generated by evolving through the beamsplitter." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The expectation value obtained (expected, there is a 60% chance of photons passing through the circuit): (1.2000000000000004+0j)\n" ] } ], "source": [ "dm_shape = lossy_output_density_matrix.shape\n", "\n", "# Constructing the number operator\n", "number_operator = np.zeros(dm_shape)\n", "for state, index in lossy_output_density_matrix.index.items():\n", " number_operator[index, index] = state.n\n", "\n", "expectation_value = (number_operator @ lossy_output_density_matrix.mat).trace()\n", "print('The expectation value obtained (expected, there is a 60% chance of photons passing through the circuit):', expectation_value)\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## III. Performing measurements on density matrix" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "A measurement on density matrix in _Perceval_ is performed by defining the modes to be measured. The process returns a dictionary with the measured states as keys with values - probability of measuring the corresponding state and remaining density matrix. In T=the following cell, measurements on the output density matrix after the beam splitter (above) is demonstrated." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The output density matrix 0.50+0.00j*|2,0><2,0|+-0.50+0.00j*|0,2><2,0|+-0.50+0.00j*|2,0><0,2|+0.50+0.00j*|0,2><0,2|\n", "state measured: |0> with probability 0.5000000000000001 and the remaining density matrix 1.00+0.00j*|2><2|\n", "state measured: |2> with probability 0.5000000000000001 and the remaining density matrix 1.00+0.00j*|0><0|\n" ] } ], "source": [ "print('The output density matrix', output_density_matrix)\n", "\n", "res = output_density_matrix.measure([0]) # performing measurements define the modes to measure\n", "for keys, values in res.items():\n", " measured_state = keys\n", " prob_meas = values[0]\n", " remaining_dm = values[1]\n", " print('state measured:', measured_state, 'with probability', prob_meas, 'and the remaining density matrix', remaining_dm)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Let's see the difference if this measurement was performed on the lossy case" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The lossy output density matrix 0.16+0.00j*|0,0><0,0|+0.24+0.00j*|1,0><1,0|+0.00+0.00j*|0,1><1,0|+0.00+0.00j*|1,0><0,1|+0.24+0.00j*|0,1><0,1|+0.18+0.00j*|2,0><2,0|+-0.18+0.00j*|0,2><2,0|+-0.18+0.00j*|2,0><0,2|+0.18+0.00j*|0,2><0,2|\n", "state measured: |0> with probability 0.5800000000000002\n", "state measured: |1> with probability 0.24000000000000005\n", "state measured: |2> with probability 0.18000000000000008\n" ] } ], "source": [ "print('The lossy output density matrix', lossy_output_density_matrix)\n", "\n", "lossy_res = lossy_output_density_matrix.measure([0]) # choose modes to measure\n", "for keys, values in lossy_res.items():\n", " print('state measured:', keys, 'with probability', values[0]) # key: measured basic state, values[0]: probability of measuring the state" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: docs/source/notebooks/Differential_equation_resolution.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "c04e3ac9", "metadata": {}, "source": [ "# Differential equation resolution\n", "\n", "## Introduction\n", "\n", "We present here a Perceval implementation of a Quantum Machine Learning algorithm for solving differential equations. Its aims is to approximate the solution to the differential equation considered in \\[1\\]:\n", "\n", "$$\n", "\\frac{d f}{d x}+\\lambda f(x)(\\kappa+\\tan (\\lambda x))=0\n", "$$\n", "\n", "with boundary condition $f(0)=f_{0}$. The analytical solution is $f(x)=f_0\\exp (-\\kappa \\lambda x) \\cos (\\lambda x)$.\n", "\n", "### QML Loss Function Definition\n", "\n", "In order to use QML to solve this differential equation, we first need to derive from it a loss function whose minimum is associated to its analytical solution.\n", "\n", "Let $F\\left[\\left\\{d^{m} f / d x^{m}\\right\\}_{m},f, x\\right]=0$ be a general differential equation verified by $f(x)$, where $F[.]$ is an operator acting on $f(x)$, its derivatives and $x$. For the solving of a differential equation, the loss function described in \\[1\\] consists of two terms\n", "\n", "$$\n", " \\mathcal{L}_{\\boldsymbol{\\theta}}\\left[\\left\\{d^{m} g / d x^{m}\\right\\}_{m},g, x\\right]:=\\mathcal{L}_{\\boldsymbol{\\theta}}^{(\\mathrm{diff})}\\left[\\left\\{d^{m} g / d x^{m}\\right\\}_{m},g, x\\right]+\\mathcal{L}_{\\boldsymbol{\\theta}}^{(\\text {boundary})}[g, x].\n", "$$\n", "\n", "The first term $\\mathcal{L}_{\\boldsymbol{\\theta}}^{(\\mathrm{diff})}$ corresponds to the differential equation which has been discretised over a fixed regular grid of $M$ points noted $x_i$:\n", "\n", "$$\n", " \\mathcal{L}_{\\boldsymbol{\\theta}}^{(\\mathrm{diff})}\\left[\\left\\{d^{m} g / d x^{m}\\right\\}_{m},g, x\\right]:=\\frac{1}{M} \\sum_{i=1}^{M} L\\left(F\\left[d_{x}^m g\\left(x_{i}\\right), g\\left(x_{i}\\right), x_{i}\\right], 0\\right),\n", "$$\n", "\n", "where $L(a,b) := (a - b)^2$ is the squared distance between two arguments. The second term $\\mathcal{L}_{\\boldsymbol{\\theta}}^{(\\text {boundary })}$ is associated to the initial conditions of our desired solution. It is defined as: \n", "\n", "$$\n", " \\mathcal{L}_{\\boldsymbol{\\theta}}^{\\text {(boundary) }}[g, x]:=\\eta L\\left(g(x_0), f_{0}\\right),\n", "$$\n", " \n", "where $\\eta$ is the weight granted to the boundary condition and $f_{0}$ is given by $f(x_0) = f_0$. \n", "\n", "Given a function approximator $f^{(n)}(x, \\boldsymbol{\\theta}, \\boldsymbol{\\lambda})$, the loss function above will be minimised using a classical algorithm, updating the parameters $\\boldsymbol{\\theta}$ based on samples obtained using a quantum device.\n", "\n", "### Quantum circuit architecture\n", "\n", "The feature map used is presented in \\[2,3,4\\]. The quantum circuit architecture from \\[4\\] is expressed as $\\mathcal{U}(x, \\boldsymbol{\\theta}):=\\mathcal{W}^{(2)}\\left(\\boldsymbol{\\theta}_{2}\\right) \\mathcal{S}(x) \\mathcal{W}^{(1)}\\left(\\boldsymbol{\\theta}_{1}\\right).$ The phase-shift operator $\\mathcal{S}(x)$ incorporates the $x$ dependency of the function we wish to approximate. It is sandwiched between two universal interferometers $\\mathcal{W}^{(1)}(\\boldsymbol{\\theta_1})$ and $\\mathcal{W}^{(2)}(\\boldsymbol{\\theta_2})$, where the beam-splitter parameters $\\boldsymbol{\\theta_1}$ and $\\boldsymbol{\\theta_2}$ of this mesh architecture are tunable to enable training of the circuit.\n", "The output measurement operator, noted $\\mathcal{M}(\\boldsymbol{\\lambda})$, is the projection on the Fock states obtained using photon-number resolving detectors, multiplied by some coefficients $\\boldsymbol{\\lambda}$ which can also be tunable. Formally, we have:\n", "\n", "$$ \\mathcal{M}(\\boldsymbol{\\lambda}) = \\sum_{\\mathbf{\\left | n^{(f)}\\right \\rangle}}\\lambda_{\\mathbf{\\left | n^{(f)}\\right \\rangle}}\\mathbf{\\left | n^{(f)}\\right \\rangle}\\mathbf{\\left \\langle n^{(f)}\\right |},\n", "$$\n", "\n", "where the sum is taken over all $\\binom{n+m-1}{n}$ possible Fock states considering $n$ photons in $m$ modes. Let $\\mathbf{\\left | n^{(i)}\\right \\rangle} = \\left |n^{(i)}_1,n^{(i)}_2,\\dots,n^{(i)}_m\\right \\rangle$ be the input state consisting of $n$ photons where $n^{(i)}_j$ is the number of photons in input mode $j$. Given these elements, the circuit's output $f^{(n)}(x, \\boldsymbol{\\theta}, \\boldsymbol{\\lambda})$ is given by the following expectation value:\n", "\n", "$$\n", "f^{(n)}(x, \\boldsymbol{\\theta}, \\boldsymbol{\\lambda})=\\left\\langle\\mathbf{n}^{(i)}\\left|\\mathcal{U}^{\\dagger}(x, \\boldsymbol{\\theta}) \\mathcal{M}(\\boldsymbol{\\lambda}) \\mathcal{U}(x, \\boldsymbol{\\theta})\\right| \\mathbf{n}^{(i)}\\right\\rangle.\n", "$$\n", "\n", "This expression can be rewritten as the following Fourier series \\[4\\]\n", "\n", "$$\n", "f^{(n)}(x, \\boldsymbol{\\theta}, \\boldsymbol{\\lambda})=\\sum_{\\omega \\in \\Omega_{n}} c_{\\omega}(\\boldsymbol{\\theta}, \\boldsymbol{\\lambda}) e^{i \\omega x},\n", "$$\n", "\n", "where $\\Omega_n = \\{-n, -n+1, \\dots, n-1, n \\}$ is the frequency spectrum one can reach with $n$ incoming photons and $\\{c_\\omega(\\boldsymbol{\\theta}, \\boldsymbol{\\lambda})\\}$ are the Fourier coefficients. The $\\boldsymbol{\\lambda}$ parameters are sampled randomly in the interval $[-a;a]$, with $a$ a randomly chosen integer. $f^{(n)}(x, \\boldsymbol{\\theta}, \\boldsymbol{\\lambda})$ will serve as a function approximator for this chosen differential equation. Differentiation in the loss function is discretised as $\\frac{df}{dx} \\simeq \\frac{f(x+\\Delta x) - f(x-\\Delta x)}{2\\Delta x}$.\n", "\n", "$n, m,$ and $\\boldsymbol{\\lambda}$ are variable parameters defined below. $\\Delta x$ is the mesh size." ] }, { "cell_type": "markdown", "id": "e9df16c8", "metadata": {}, "source": [ "## Perceval Simulation\n", "\n", "### Initialisation" ] }, { "cell_type": "code", "execution_count": 1, "id": "7918962c", "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "import numpy as np\n", "from math import comb, pi\n", "from scipy.optimize import minimize\n", "import time\n", "import matplotlib.pyplot as plt\n", "import matplotlib as mpl\n", "import tqdm as tqdm" ] }, { "cell_type": "markdown", "id": "04323b71", "metadata": {}, "source": [ "We will run this notebook with 4 photons. We could use more photons, but the result with 4 photons is already satisfying." ] }, { "cell_type": "code", "execution_count": 2, "id": "f59b62f8", "metadata": {}, "outputs": [], "source": [ "nphotons = 4" ] }, { "cell_type": "markdown", "id": "bd3c3eaf", "metadata": {}, "source": [ "### Differential equation parameters\n", "\n", "We define here the value of the differential equation parameters and boundary condition $\\lambda, \\kappa, f_0$." ] }, { "cell_type": "code", "execution_count": 3, "id": "debd15cc", "metadata": {}, "outputs": [], "source": [ "# Differential equation parameters\n", "lambd = 8\n", "kappa = 0.1\n", "\n", "def F(u_prime, u, x): # DE, works with numpy arrays\n", " return u_prime + lambd * u * (kappa + np.tan(lambd * x))" ] }, { "cell_type": "code", "execution_count": 4, "id": "4c18efbf", "metadata": {}, "outputs": [], "source": [ "# Boundary condition (f(x_0)=f_0)\n", "x_0 = 0\n", "f_0 = 1" ] }, { "cell_type": "code", "execution_count": 5, "id": "ac67fd52", "metadata": {}, "outputs": [], "source": [ "# Modeling parameters\n", "n_grid = 50 # number of grid points of the discretized differential equation\n", "range_min = 0 # minimum of the interval on which we wish to approximate our function\n", "range_max = 1 # maximum of the interval on which we wish to approximate our function\n", "X = np.linspace(range_min, range_max-range_min, n_grid) # Optimisation grid" ] }, { "cell_type": "code", "execution_count": 6, "id": "45a02405", "metadata": {}, "outputs": [], "source": [ "# Differential equation's exact solution - for comparison\n", "def u(x):\n", " return np.exp(- kappa*lambd*x)*np.cos(lambd*x)" ] }, { "cell_type": "code", "execution_count": 7, "id": "9c8830ee", "metadata": {}, "outputs": [], "source": [ "# Parameters of the quantum machine learning procedure\n", "N = nphotons # Number of photons\n", "m = nphotons # Number of modes\n", "eta = 5 # weight granted to the initial condition\n", "a = 200 # Approximate boundaries of the interval that the image of the trial function can cover\n", "fock_dim = comb(N + m - 1, N)\n", "# lambda coefficients for all the possible outputs\n", "lambda_random = 2 * a * np.random.rand(fock_dim) - a\n", "# dx serves for the numerical differentiation of f\n", "dx = (range_max-range_min) / (n_grid - 1)" ] }, { "cell_type": "code", "execution_count": 8, "id": "355b87c8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "|1,1,1,1>\n" ] } ], "source": [ "# Input state with N photons and m modes\n", "input_state = pcvl.BasicState([1]*N+[0]*(m-N))\n", "print(input_state)" ] }, { "cell_type": "markdown", "id": "58385605", "metadata": {}, "source": [ "## Definition of the circuit\n", "\n", "We will generate a Haar-random initial unitary using QR decomposition built in Perceval `Matrix.random_unitary`, the circuit is defined by the combination of 3 sub-circuits - the intermediate phase is a parameter." ] }, { "cell_type": "code", "execution_count": 9, "id": "5dd4d6c3", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "W1\n", "\n", "\n", "Φ=px\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "W2\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"Haar unitary parameters\"\n", "# number of parameters used for the two universal interferometers (2*m**2 per interferometer)\n", "parameters = np.random.normal(size=4*m**2)\n", "\n", "px = pcvl.P(\"px\")\n", "c = pcvl.Unitary(pcvl.Matrix.parametrized_unitary(m, parameters[:2 * m ** 2]), name=\"W1\")\\\n", " // (0, pcvl.PS(px))\\\n", " // pcvl.Unitary(pcvl.Matrix.parametrized_unitary(m, parameters[2 * m ** 2:]), name=\"W2\")\n", "\n", "backend = pcvl.BackendFactory().get_backend(\"SLOS\")\n", "backend.set_circuit(pcvl.Unitary(pcvl.Matrix.random_unitary(m)))\n", "backend.preprocess([input_state])\n", "\n", "pcvl.pdisplay(c)" ] }, { "cell_type": "markdown", "id": "5d9333b0", "metadata": {}, "source": [ "### Expectation value and loss function computation\n", "\n", "The expectation value of the measurement operator $\\mathcal{M}(\\boldsymbol{\\lambda})$ is obtained directly from Fock state probabilities computed by Perceval. Given this expectation value, the code snippet below computes the loss function defined in the Introduction.\n", "\n", "Note the use of the `all_prob` simulator method giving directly access to the probabilities of all possible output states, including null probabilities. This calculation is optimized in SLOS backend." ] }, { "cell_type": "code", "execution_count": 10, "id": "597cce98", "metadata": {}, "outputs": [], "source": [ "def computation(params):\n", " global current_loss\n", " global computation_count\n", " \"compute the loss function of a given differential equation in order for it to be optimized\"\n", " computation_count += 1\n", " f_theta_0 = 0 # boundary condition\n", " coefs = lambda_random # coefficients of the M observable\n", " # initial condition with the two universal interferometers and the phase shift in the middle\n", " U_1 = pcvl.Matrix.parametrized_unitary(m, params[:2 * m ** 2])\n", " U_2 = pcvl.Matrix.parametrized_unitary(m, params[2 * m ** 2:])\n", "\n", " px = pcvl.P(\"x\")\n", " c = pcvl.Unitary(U_2) // (0, pcvl.PS(px)) // pcvl.Unitary(U_1)\n", "\n", " px.set_value(pi * x_0)\n", " backend.set_circuit(c)\n", " f_theta_0 = np.sum(np.multiply(backend.all_prob(input_state), coefs))\n", "\n", " # boundary condition given a weight eta\n", " loss = eta * (f_theta_0 - f_0) ** 2 * len(X)\n", "\n", " # Y[0] is before the domain we are interested in (used for differentiation), x_0 is at Y[1]\n", " Y = np.zeros(n_grid + 2)\n", "\n", " # x_0 is at the beginning of the domain, already calculated\n", " Y[1] = f_theta_0\n", "\n", " px.set_value(pi * (range_min - dx))\n", " backend.set_circuit(c)\n", " Y[0] = np.sum(np.multiply(backend.all_prob(input_state), coefs))\n", "\n", "\n", " for i in range(1, n_grid):\n", " x = X[i]\n", " px.set_value(pi * x)\n", " backend.set_circuit(c)\n", " Y[i + 1] = np.sum(np.multiply(backend.all_prob(input_state), coefs))\n", "\n", " px.set_value(pi * (range_max + dx))\n", " backend.set_circuit(c)\n", " Y[n_grid + 1] = np.sum(np.multiply(backend.all_prob(input_state), coefs))\n", "\n", " # Differentiation\n", " Y_prime = (Y[2:] - Y[:-2])/(2*dx)\n", "\n", " loss += np.sum((F(Y_prime, Y[1:-1], X))**2)\n", "\n", " current_loss = loss / len(X)\n", " return current_loss" ] }, { "cell_type": "markdown", "id": "8acf5506", "metadata": {}, "source": [ "### Classical optimisation\n", "\n", "Finally the code below performs the optimisation procedure using the loss function defined in the previous section. To this end, we use a Broyden–Fletcher–Goldfarb–Shanno (BFGS) optimiser \\[5\\] from the SciPy library." ] }, { "cell_type": "code", "execution_count": 11, "id": "281dbb29", "metadata": {}, "outputs": [], "source": [ "def callbackF(parameters):\n", " \"\"\"callback function called by scipy.optimize.minimize allowing to monitor progress\"\"\"\n", " global current_loss\n", " global computation_count\n", " global loss_evolution\n", " global start_time\n", " now = time.time()\n", " pbar.set_description(\"M= %d Loss: %0.5f #computations: %d elapsed: %0.5f\" % \n", " (m, current_loss, computation_count, now-start_time))\n", " pbar.update(1)\n", " loss_evolution.append((current_loss, now-start_time))\n", " computation_count = 0\n", " start_time = now" ] }, { "cell_type": "code", "execution_count": null, "id": "465db5d2", "metadata": {}, "outputs": [], "source": [ "computation_count = 0\n", "current_loss = 0\n", "start_time = time.time()\n", "loss_evolution = []\n", "\n", "pbar = tqdm.tqdm()\n", "res = minimize(computation, parameters, callback=callbackF, method='BFGS', options={'gtol': 1E-2})" ] }, { "cell_type": "markdown", "id": "1be681a8", "metadata": {}, "source": [ "After the optimisation procedure has been completed, the optimal unitary parameters (in `res.x`) can be used to determine the quantum circuit beam-splitter and phase-shifter angles for an experimental realisation." ] }, { "cell_type": "code", "execution_count": 13, "id": "21726b0c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Unitary parameters [ 1.90624035 0.15201242 -0.82869314 0.89828783 0.6380241 -1.04604715\n", " 4.40125344 -0.11807653 0.84899534 0.78434718 -0.25534406 3.60915677\n", " -0.66390566 -1.92075941 -3.3959869 4.64905094 2.68642653 -0.09049131\n", " -0.55289317 3.96738349 -2.48499145 1.94691379 0.81546265 -4.27745458\n", " -0.48299482 -2.61704687 -1.32399656 0.19826926 0.38186777 -1.24266346\n", " -0.35951725 -3.81589427 -0.54128274 1.5325363 0.3096436 -1.70350065\n", " -2.4345667 0.01430551 -0.92837642 1.77323448 0.42465747 0.48688715\n", " 1.17728595 -0.63465766 0.2021728 -0.21649088 -2.16424414 -1.06376279\n", " -0.83562031 0.86918988 1.83050536 0.34868688 -0.53611804 1.45509538\n", " 1.93232455 -0.08290686 0.14500095 0.24973801 -2.61256259 0.3786195\n", " -0.95858293 -0.27414401 -1.21134094 -1.27487461]\n" ] } ], "source": [ "print(\"Unitary parameters\", res.x)" ] }, { "cell_type": "markdown", "id": "3b6c5da8", "metadata": {}, "source": [ "### Plotting the approximation\n", "\n", "We now plot the result of our optimisation in order to compare the QML algorithm's output and the analytical solution." ] }, { "cell_type": "code", "execution_count": 14, "id": "6bede765", "metadata": {}, "outputs": [], "source": [ "def plot_solution(m, N, X, optim_params, lambda_random):\n", " Y = []\n", " U_1 = pcvl.Matrix.parametrized_unitary(m, optim_params[:2 * m ** 2])\n", " U_2 = pcvl.Matrix.parametrized_unitary(m, optim_params[2 * m ** 2:])\n", " px = pcvl.P(\"x\")\n", " c = pcvl.Unitary(U_2) // (0, pcvl.PS(px)) // pcvl.Unitary(U_1)\n", "\n", " for x in X:\n", " px.set_value(pi * x)\n", " backend.set_circuit(c)\n", " f_theta = np.sum(np.multiply(backend.all_prob(input_state), lambda_random))\n", " Y.append(f_theta)\n", " exact = u(X)\n", " plt.plot(X, Y, label=\"Approximation with {} photons\".format(N))" ] }, { "cell_type": "code", "execution_count": 15, "id": "b997c635", "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAABCcAAAMOCAYAAAAtMWzUAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy80BEi2AAAACXBIWXMAAA9hAAAPYQGoP6dpAADOH0lEQVR4nOzdd1zU9QPH8dcde4OKgIri3iP3qLS0XJkNc5Qzs9KyzLS0TNOszNSsNCtzNly5Ss1UFC333nsPhqiAgMy73x+XFD8XKvBlvJ+Pxz3Cu+997/0FQu/NZ5isVqsVERERERERERGDmI0OICIiIiIiIiL5m8oJERERERERETGUygkRERERERERMZTKCRERERERERExlMoJERERERERETGUygkRERERERERMZTKCRERERERERExlL3RAYxgsVi4cOECHh4emEwmo+OIiIiIiIiI5ElWq5WrV69SpEgRzOZbj4/Il+XEhQsXCAwMNDqGiIiIiIiISL5w9uxZihUrdsvH82U54eHhAdg+OZ6enganEREREREREcmbYmJiCAwMTHsffiv5spy4PpXD09NT5YSIiIiIiIhIFrvTkgpaEFNEREREREREDKVyQkREREREREQMpXJCRERERERERAyVL9ecEBERERGRzJWamkpycrLRMUQkmzk4OGBnZ3ff51E5ISIiIiIi98xqtRIWFkZUVJTRUUTEIN7e3vj7+99x0cvbUTkhIiIiIiL37HoxUbhwYVxdXe/rzYmI5C5Wq5X4+HgiIiIACAgIuOdzqZwQEREREZF7kpqamlZMFCxY0Og4ImIAFxcXACIiIihcuPA9T/HQgpgiIiIiInJPrq8x4erqanASETHS9Z8B97PujMoJERERERG5L5rKIZK/ZcbPAJUTIiIiIiIiImIolRMiIiIiIiK5TPfu3XnqqacMee1Tp05hMpnYtWuXIa9/MyEhIZhMpjvuGhMUFMT48eOzJdOdNGnShH79+hkdI8dQOSEiIiIiIvnSxo0bsbOzo3Xr1kZHuWtffvkl06dPz/LXuVkJEhgYSGhoKFWqVMny18+ohg0bEhoaipeXFwDTp0/H29s7019n1KhRmEymHFMqfPjhh9SoUcPoGJlC5YSIiIiIiORLU6ZMoW/fvqxbt44LFy5ky2smJSVlynm8vLyy5M13RtjZ2eHv74+9fc7Z/NHR0RF/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a0apUqXSnjdz5kyWLVvGjz/+iL29PW5ubvz0009Mnjw53Vag9+uVV17hmWeeoUOHDtSrV49Lly6lLRR5Xa9evShfvjy1a9fG19eX9evX33CeokWLsmzZMrZs2UL16tV59dVX6dmzZ9rinPejcePGpKamppUTZrOZhx9+OO23+rfSsGFDXn31VTp06ICvry+jR4++7yx3q2nTppQtW5aHH36YDh068OSTT952+9T/d6fvE7B9z7Zo0YJHHnkEX19fZs2alaHn3Ymnpyfr1q2jVatWlCtXjiFDhjB27Fhatmx5N5+Cu2KyZuVeIDlUTEwMXl5eREdH4+npaXQcEREREZFcKSEhgZMnT1KyZMm7XiwwpwsJCeGRRx7hypUreHt73/SY6dOn069fP6KiorI1m+R83bt3JyoqikWLFhkdJVvc7mdBRt9/a+SEiIiIiIiIiBhK5YSIiIiIiIiIGErTOjStQ0RERETknuTlaR0iknGa1iEiIiIiIiIiuZ7KCRERERERERExVJaWE+vWraNNmzYUKVIEk8mUoZVKQ0JCqFmzJk5OTpQpU4bp06ffcMzEiRMJCgrC2dmZevXqsWXLlswPLyIiIiIiIiLZIkvLibi4OKpXr87EiRMzdPzJkydp3bo1jzzyCLt27aJfv3689NJL/Pnnn2nHzJkzh/79+zNs2DB27NhB9erVad68OREREVl1GSIiIiIiIiKShbJtQUyTycTChQt56qmnbnnMu+++y9KlS9m3b1/afR07diQqKorly5cDUK9ePerUqcOECRMAsFgsBAYG0rdvXwYNGpShLLlxQUzr5MmYSpSAZs3ArNk4IiIiImI8LYgpIpAHF8TcuHEjzZo1S3df8+bN2bhxIwBJSUls37493TFms5lmzZqlHXMziYmJxMTEpLvlKlevkvjmW9C8OSlly8HYsXD5stGpRERERERERDJFjionwsLC8PPzS3efn58fMTExXLt2jcjISFJTU296TFhY2C3P++mnn+Ll5ZV2CwwMzJL8WeXshUvMqfgIMY6u2J84DgMGYClaFLp3hwMHjI4nIiIiIiK3EBQUxPjx4+/rHCEhIZhMJqKiojIl06lTpzCZTOzatStTzvf/MitvVueUnCVHlRNZZfDgwURHR6fdzp49a3Sku1KsXAlKzprK66N/Y1Dz19lfuBTmhASYMQNr1arw8stw4YLRMUVEREREcpWNGzdiZ2dH69atjY6SpkmTJvTr1y/dfQ0bNiQ0NBQvLy9jQmWD7t2737AEQGBgIKGhoVSpUsWYUJKtclQ54e/vT3h4eLr7wsPD8fT0xMXFhUKFCmFnZ3fTY/z9/W95XicnJzw9PdPdchOTycTD5XyZ+WZT2n07nLGjZvF05zEsL9cAk8UCkydjLVsWhg6Fq1eNjisiIiIikitMmTKFvn37sm7dOi7k4F/2OTo64u/vj8lkMjpKtrKzs8Pf3x97e3ujo0g2yFHlRIMGDQgODk5338qVK2nQoAFg+5+yVq1a6Y6xWCwEBwenHZPX1Q4qwNQedRn52UvMGTSeZ18YzfYiFTDFx8NHH9lKit9+MzqmiIiIiEiOFhsby5w5c+jduzetW7dm+vTp6R6/PjUhODiY2rVr4+rqSsOGDTl8+HDaMcePH6dt27b4+fnh7u5OnTp1WLVq1S1f88UXX+SJJ55Id19ycjKFCxdmypQpdO/enbVr1/Lll19iMpkwmUycOnXqptMk1q9fT5MmTXB1dcXHx4fmzZtz5coVAJYvX86DDz6It7c3BQsW5IknnuD48eN39fn55ptvKFu2LM7Ozvj5+dGuXbu0xxITE3njjTcoXLgwzs7OPPjgg2zduvWW5/rwww+pUaNGuvvGjx9PUFBQ2uMzZsxg8eLFadcdEhJy02kda9eupW7dujg5OREQEMCgQYNISUlJe7xJkya88cYbvPPOOxQoUAB/f38+/PDDu7p2MUaWlhOxsbHs2rUr7Zvp5MmT7Nq1izNnzgC26RZdu3ZNO/7VV1/lxIkTvPPOOxw6dIhvvvmGuXPn8tZbb6Ud079/fyZPnsyMGTM4ePAgvXv3Ji4ujh49emTlpeQ4lYt4Ma1HXfq835X+b07klafe44RPEUzh4dC2rW09ikyakyYiIiIikiFWK8TFGXO7y00I586dS4UKFShfvjydO3dm6tSp3Gwjw/fff5+xY8eybds27O3tefHFF9Mei42NpVWrVgQHB7Nz505atGhBmzZt0t7v/L+XXnqJ5cuXExoamnbfkiVLiI+Pp0OHDnz55Zc0aNCAXr16ERoaSmho6E3Xy9u1axdNmzalUqVKbNy4kb///ps2bdqQmpoKQFxcHP3792fbtm0EBwdjNpt5+umnsVgsGfrcbNu2jTfeeIMRI0Zw+PBhli9fzsMPP5z2+DvvvMP8+fOZMWMGO3bsoEyZMjRv3pzL97ho/4ABA2jfvj0tWrRIu+6GDRvecNz58+dp1aoVderUYffu3UyaNIkpU6YwcuTIdMfNmDEDNzc3Nm/ezOjRoxkxYgQrV668p2ySjaxZaM2aNVbghlu3bt2sVqvV2q1bN2vjxo1veE6NGjWsjo6O1lKlSlmnTZt2w3m//vpra/Hixa2Ojo7WunXrWjdt2nRXuaKjo62ANTo6+h6vLGe5lpRi/Tr4iLXqoEXWSXWfsaZislrBailWzGpdudLoeCIiIiKSR127ds164MAB67Vr12x3xMZarbaaIPtvsbF3lb1hw4bW8ePHW61WqzU5OdlaqFAh65o1a9Iev/5eZtWqVWn3LV261Ar8e703UblyZevXX3+d9ucSJUpYv/jii7Q/V6pUyfrZZ5+l/blNmzbW7t27p/25cePG1jfffDPdOa9nuXLlitVqtVo7depkbdSoUYav9eLFi1bAunfvXqvVarWePHnSClh37tx50+Pnz59v9fT0tMbExNzwWGxsrNXBwcH6888/p92XlJRkLVKkiHX06NE3zTts2DBr9erV053niy++sJYoUSLtz926dbO2bds23TH/n/O9996zli9f3mqxWNKOmThxotXd3d2amppqtVptn78HH3ww3Xnq1Kljfffdd296rZI5bvhZ8B8Zff+dpSMnmjRpgtVqveF2fcjU9OnTCQkJueE5O3fuJDExkePHj9O9e/cbzvv6669z+vRpEhMT2bx5M/Xq1cvKy8jxnB3seP3Rsiwd+Birur/Ncy98xinvAEznzsFjj8Hbb8N/hjqJiIiIiORnhw8fZsuWLXTq1AkAe3t7OnTowJQpU244tlq1amkfBwQEABAREQHYRk4MGDCAihUr4u3tjbu7OwcPHrzlyAmwjZ6YNm0aYFs7748//kg3GiMjro+cuJWjR4/SqVMnSpUqhaenZ9r0idvl+q/HHnuMEiVKUKpUKbp06cLPP/9MfHw8YJvKkpycTKNGjdKOd3BwoG7duhw8ePCuruNuHTx4kAYNGqRbe6NRo0bExsZy7ty5tPv++zUD29ft+tdMci6tLJKHBBZwZfbL9fmmnC9P+Jdm4JqpdNuxFMaNgz17YM4cKFDA6JgiIiIikle5ukJsrHGvnUFTpkwhJSWFIkWKpN1ntVpxcnJiwoQJ6XbFcHBwSPv4+pvi69MjBgwYwMqVKxkzZgxlypTBxcWFdu3akZSUdMvX7tq1K4MGDWLjxo1s2LCBkiVL8tBDD2U4O4CLi8ttH2/Tpg0lSpRg8uTJFClSBIvFQpUqVW6b6788PDzYsWMHISEhrFixgqFDh/Lhhx/edl2J2zGbzTdMmUlOTr6nc2XEf79mYPu6ZXRKixgnRy2IKffP3s7MG03L8uMbjzC149u8+tRg4hycYdUqqFMH9u0zOqKIiIiI5FUmE7i5GXPL4E4WKSkpzJw5k7Fjx6atj7dr1y52795NkSJFmDVrVoYvd/369XTv3p2nn36aqlWr4u/vz6lTp277nIIFC/LUU08xbdo0pk+ffsPaeY6OjmlrR9xKtWrVbthI4LpLly5x+PBhhgwZQtOmTalYsWLaQpl3w97enmbNmjF69Gj27NnDqVOnWL16NaVLl8bR0ZH169enHZucnMzWrVupVKnSTc/l6+tLWFhYuoLiv4tcQsauu2LFimzcuDHdedavX4+HhwfFihW762uUnEXlRB71QHEflr3xEHbt2vFs58856+UHJ05grV8fFi40Op6IiIiIiCGWLFnClStX6NmzJ1WqVEl3e/bZZ286teNWypYty4IFC9LKjeeffz5Dv6F/6aWX0hb479atW7rHgoKC2Lx5M6dOnSIyMvKm5xs8eDBbt26lT58+7Nmzh0OHDjFp0iQiIyPx8fGhYMGCfP/99xw7dozVq1fTv3//DF8T2D5HX331Fbt27eL06dPMnDkTi8VC+fLlcXNzo3fv3gwcOJDly5dz4MABevXqRXx8PD179rzp+Zo0acLFixcZPXo0x48fZ+LEifzxxx83XPeePXs4fPgwkZGRNx1Z0adPH86ePUvfvn05dOgQixcvZtiwYfTv3x+zWW9tczt9BfMwNyd7Jjz/AC2fb86TXcexvkQ1THFx8MwzMHGi0fFERERERLLdlClTaNasWbqpG9c9++yzbNu2jT179mToXOPGjcPHx4eGDRvSpk0bmjdvTs2aNe/4vGbNmhEQEEDz5s3TTS0B21QROzs7KlWqhK+v703XiShXrhwrVqxg9+7d1K1blwYNGrB48WLs7e0xm83Mnj2b7du3U6VKFd566y0+//zzDF3Pdd7e3ixYsIBHH32UihUr8u233zJr1iwqV64MwKhRo3j22Wfp0qULNWvW5NixY/z555/4+Pjc9HwVK1bkm2++YeLEiVSvXp0tW7YwYMCAdMf06tWL8uXLU7t2bXx9fdONzLiuaNGiLFu2jC1btlC9enVeffVVevbsyZAhQ+7q+iRnMln/f/JPPhATE4OXlxfR0dF4enoaHSdb/LE3lIGzd/DOH5PounOp7c6PP4bBgzM8BE5ERERE5L8SEhI4efIkJUuWxNnZ2eg4uUZsbCxFixZl2rRpPPPMM0bHEblvt/tZkNH331oQM59oWTWAEgUfope7M1dcPHlzwyx4/32IioLPPlNBISIiIiKSxSwWC5GRkYwdOxZvb2+efPJJoyOJ5BgqJ/KRSkU8WfBaI7o42xPj5MoHa6bA559DdDR88w3Y2RkdUUREREQkzzpz5gwlS5akWLFiTJ8+HXt7vR0TuU7/N+Qzfp7OzHm5Ad0d7HjHyY1P/5yA3fffQ3w8zJgBWkhGRERERCRLBAUF3bClpojY6J1oPuTj5sjPvepz5umO9H3yHZLNdvDTT/D666AfliIiIiIiIpLNVE7kU+5O9kzvUZekp5/h7db9sWCCSZNs61CIiIiIiNwFjQYQyd8y42eAyol8zNnBjkmda3GtXXveb/6a7c5PP7UtkCkiIiIicgcODg4AxMfHG5xERIx0/WfA9Z8J90JrTuRzDnZmJjz/AC8lp/JJYhzvhUyDQYPA2xteecXoeCIiIiKSg9nZ2eHt7U1ERAQArq6umLQLnEi+YbVaiY+PJyIiAm9vb+zuY5MFlROCk70d33WpRdekl5iQGMfrG+di7d0bU+HC8PTTRscTERERkRzM398fIK2gEJH8x9vbO+1nwb1SOSEAuDraM7VHHZ5Pfg3va1fpvOsPLJ07Y/77b3jgAaPjiYiIiEgOZTKZCAgIoHDhwiQnJxsdR0SymYODw32NmLhO5YSk8XR2YGbP+nRKfJvA6HAan9yB5Yk2mLdugSJFjI4nIiIiIjmYnZ1dprxBEZH8SQtiSjoF3ByZ2qshw14YytGCgZgvnMfati1okSMRERERERHJIion5AZFvV34+pUm9Ok4nMsunpi2bcParRtYLEZHExERERERkTxI5YTcVNViXgzs3ZJXn3mPJLM9pl9/hQ8/NDqWiIiIiIiI5EEqJ+SWHq/sz+OvPMfgFn1td3z0ESxdamwoERERERERyXNUTsht9XywJM4v9WB6zScASO3cGU6fNjiViIiIiIiI5CUqJ+S2TCYTw5+sTPCLA9kVUA67qChSn3sOEhONjiYiIiIiIiJ5hMoJuSN7OzPjutbjwxeGEeXsjt3WrVjfftvoWCIiIiIiIpJHqJyQDPH1cOKD11sy8ElbKWGaOBHmzDE4lYiIiIiIiOQFKickw2qV8OHhft2Z0KA9AKk9e8KRIwanEhERERERkdxO5YTclc71S3Dq9YFsLF4Vu7g4kjt2guRko2OJiIiIiIhILqZyQu6KyWTio3YPMLHHUKKc3XHYuQPriBFGxxIREREREZFcTOWE3DUXRzuGv9aC4a36AmD95BPYsMHgVCIiIiIiIpJbqZyQe1La1536g15lfuVHMFssJHZ6Aa5eNTqWiIiIiIiI5EIqJ+Seta8dyPo3h3HOszBOZ06R3PcNoyOJiIiIiIhILqRyQu6ZyWRi2AsN+aTDICyYcJgxHRYuNDqWiIiIiIiI5DIqJ+S+eLk60G1QV76r/ywAiT1fgshIg1OJiIiIiIhIbqJyQu5bvVIFSRgylIO+QThducy11/oaHUlERERERERyEZUTkileb1GZyV3fI9VkxmXubKxLlhgdSURERERERHIJlROSKRzszLwyoCPT6j4NwLWXXoaYGINTiYiIiIiISG6gckIyTXl/D1KHDeOkTwCu4aHEvzXA6EgiIiIiIiKSC6ickEzV8/HKfN95MACuUydjDQkxNpCIiIiIiIjkeConJFPZ25np/l53Zj3QEoC4rj3g2jWDU4mIiIiIiEhOpnJCMl15fw9ih39MqHtB3M+eIvaDD42OJCIiIiIiIjmYygnJEj1aVWdKx7cBcB4/Dg4fNjiRiIiIiIiI5FQqJyRL2NuZefrD3qwuXQf71BQu93gZrFajY4mIiIiIiEgOpHJCskzlot4cHPwRCfaOFNi4jsRZs42OJCIiIiIiIjmQygnJUt07N+XHxp0ASHqjH1y9amwgERERERERyXFUTkiWcnOyp/TnwznlHYDHpQguD3zP6EgiIiIiIiKSw6ickCz36AMl+O2lQQB4TZ6EZfcegxOJiIiIiIhITqJyQrLFc0N6sbJCI+wsqUR276XFMUVERERERCSNygnJFgFeLlwa+RnX7J0ovGsLV3/S4pgiIiIiIiJio3JCsk27pxrwa7PnAUh5ewAkJBicSERERERERHIClROSbeztzFQYM5wLHoXwuXiB8OGfGh1JREREREREcgCVE5Kt6lQOZGXnNwHw/GIM1tBQgxOJiIiIiIiI0VROSLZ7bORb7C5SDpfEeM689rbRcURERERERMRgKick2xUp4MaRd0cAELhoNte2bjM4kYiIiIiIiBhJ5YQYok3vdqyq/ghmq5WLL72mrUVFRERERETyMZUTYghnBzvsR39Ggr0jxfds4eJPc42OJCIiIiIiIgZROSGGafxYbf58vBMAqe++CykpBicSERERERERI6icEMOYTCYqfDGSyy6e+Iee5vTYiUZHEhEREREREQOonBBDlS9XjL/avwyA+6cjscbFGZxIREREREREspvKCTFcvdFDOO9VmILRkRwZ8qnRcURERERERCSbqZwQw/kX9mJ3r7cBKPrteJIvRhqcSERERERERLKTygnJER768A2O+JXCPSGOI/3eMzqOiIiIiIiIZCOVE5IjeLg5c3bgEADKzJ1O7NETBicSERERERGR7KJyQnKMh9/syq5S1XFKSebk6wONjiMiIiIiIiLZROWE5BgO9nYkjPwEgEorFxG5bbfBiURERERERCQ7qJyQHKVex5ZsqfYQdlYLF94abHQcERERERERyQYqJyRHMZlMOH/yEQBV/l5O6N9bDU4kIiIiIiIiWU3lhOQ41Vo3ZkutRzFjJWKARk+IiIiIiIjkdSonJEfyHDUSCyaqbw7mdPB6o+OIiIiIiIhIFlI5ITlShWYN2Fb/cQCiBr5ncBoRERERERHJSionJMfyHfMJqSYz1Xeu4+iS1UbHERERERERkSyickJyrJKNarLjoVYAxA8eYnAaERERERERySoqJyRHKzLmY1JMZqrv28i+eX8YHUdERERERESygMoJydGK1qnGzkfbApDy4XCD04iIiIiIiEhWUDkhOV7xMSNJMZmpcWAzexeuNDqOiIiIiIiIZDKVE5Lj+dWoxJ7GrQFIHP6RwWlEREREREQks6mckFyh6OiPsGCi9u6/2Lt0rdFxREREREREJBOpnJBcwa9OdfY1ag5A3LARBqcRERERERGRzKRyQnKNwp/ZFsSsu30Ne1ZuNDiNiIiIiIiIZBaVE5Jr+Deqy/66j2LGSsxQjZ4QERERERHJK1ROSK5S8FNbKdFg8wr2rt1ucBoRERERERHJDConJFfxf7QRhx94EDurhUtDhhsdR0RERERE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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "X = np.linspace(range_min, range_max, 200)\n", "\n", "# Change the plot size\n", "default_figsize = mpl.rcParamsDefault['figure.figsize']\n", "mpl.rcParams['figure.figsize'] = [2 * value for value in default_figsize]\n", "\n", "plot_solution(m, N, X, res.x, lambda_random)\n", "\n", "plt.plot(X, u(X), 'r', label='Analytical solution')\n", "plt.legend()\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 16, "id": "f819bb4b", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Loss function value')" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAABDIAAAMiCAYAAACc2OM1AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy80BEi2AAAACXBIWXMAAA9hAAAPYQGoP6dpAACLMElEQVR4nOzdeXjU5b3+8XuWJJN9TyAhIewYwIQlRFBULJbS1rW1Lj1KsaVbqLYce6q/Vm21ra0eLccyLXVBtG6oVVzrhiKgYAIIKpE9hLBk37dJZvn9ERKlBGRgwneW9+u65iL5zuSbO0Utc/M8n8fk8Xg8AgAAAAAACABmowMAAAAAAACcKIoMAAAAAAAQMCgyAAAAAABAwKDIAAAAAAAAAYMiAwAAAAAABAyKDAAAAAAAEDAoMgAAAAAAQMCgyAAAAAAAAAHDanQAf+Z2u3Xw4EHFxsbKZDIZHQcAAAAAgKDk8XjU0tKijIwMmc3HX3NBkXEcBw8eVFZWltExAAAAAAAICRUVFRoyZMhxX0OR0Q+73S673S6n0ymp53/IuLg4g1MBAAAAABCcmpublZWVpdjY2C99rcnj8XhOQ6aA1NzcrPj4eDU1NVFkAAAAAAAwQLx5/82wTwAAAAAAEDAoMgAAAAAAQMCgyOiH3W5Xbm6uCgoKjI4CAAAAAAC+gBkZx8GMDAAAAAAABh4zMgAAAAAAQFCiyAAAAAAAAAGDIgMAAAAAAAQMiox+MOwTAAAAAAD/xLDP42DYJwAAAAAAA49hnwAAAAAAIChRZAAAAAAAgIBBkQEAAAAAAAIGRQYAAAAAAAgYFBn94NQSAAAAAAD8E6eWHAenlgAAAAAAMPA4tQQAAAAAAAQligwAAAAAABAwKDIAAAAAAEDAoMgAAAAAAAABgyIDAAAAAAAEDIqMfnD8KgAAAAAA/onjV4+D41cBAAAAABh4HL8KAAAAAACCEkUGAAAAAAAIGBQZAAAAAAAgYFBkAAAAAACAgEGRAQAAAAAAAgZFBgAAAAAACBgUGQAAAAAAIGBQZPTDbrcrNzdXBQUFRkfx2pL3dmviHW/qrtc+MzoKAAAAAAA+R5HRj6KiIpWWlqqkpMToKF7zeKSG9m7VtDqMjgIAAAAAgM9RZASZpOgwSVJDW5fBSQAAAAAA8D2KjCCTFB0hSaqnyAAAAAAABCGKjCDTuyKjvp0iAwAAAAAQfCgygkzfioxWigwAAAAAQPChyAgySVHhkqS2Lpc6u10GpwEAAAAAwLcoMoJMXKRVVrNJktTA9hIAAAAAQJChyAgyJpNJidE9qzLq2F4CAAAAAAgyFBlBqHd7CSsyAAAAAADBhiIjCCUdXpHBEawAAAAAgGBDkdEPu92u3NxcFRQUGB3lpFBkAAAAAACCFUVGP4qKilRaWqqSkhKjo5wUigwAAAAAQLCiyAhCiRQZAAAAAIAgRZERhJIpMgAAAAAAQYoiIwixtQQAAAAAEKwoMoIQRQYAAAAAIFhRZAQhigwAAAAAQLCiyAhCvUVGQ3uX3G6PwWkAAAAAAPAdiowglBjVU2S4PVJTR7fBaQAAAAAA8B2KjCAUbjUrNsIqSapvZ3sJAAAAACB4UGQEqaQY5mQAAAAAAIIPRUaQ6t1eQpEBAAAAAAgmFBlBKpmTSwAAAAAAQYgiI0hxBCsAAAAAIBhRZAQpigwAAAAAQDCiyOiH3W5Xbm6uCgoKjI5y0igyAAAAAADBiCKjH0VFRSotLVVJSYnRUU5aIkUGAAAAACAIUWQEKYZ9AgAAAACCEUVGkGJFBgAAAAAgGFFkBClWZAAAAAAAghFFRpDqHfbZ0e1SR5fL4DQAAAAAAPgGRUaQiomwKsxikiTVt7MqAwAAAAAQHCgygpTJZPr8CNZWigwAAAAAQHCgyAhiiVGHiwxWZAAAAAAAggRFRhBLjukd+OkwOAkAAAAAAL5BkRHE+lZktHUbnAQAAAAAAN+gyAhinx/ByooMAAAAAEBwoMgIYonRrMgAAAAAAAQXiowgxooMAAAAAECwocgIYknREZKkBlZkAAAAAACCBEVGEEuMDpMk1bEiAwAAAAAQJCgygljy4RUZ9W1dBicBAAAAAMA3KDKCWO+KjMaObrncHoPTAAAAAABw6igyglhiVM+wT49HamxnVQYAAAAAIPBRZASxMItZcTarJKmBIgMAAAAAEAQoMoJc4uEjWBvbObkEAAAAABD4rEYHOB1ycnIUFxcns9msxMREvfvuu0ZHOm0SIsNULooMAAAAAEBwCIkiQ5I++OADxcTEGB3jtIs/PCejsYMiAwAAAAAQ+NhaEuQSow6fXMKMDAAAAABAEPD7ImP16tW66KKLlJGRIZPJpBUrVhz1GrvdrpycHNlsNhUWFqq4uPiI500mk8477zwVFBToiSeeOE3J/UNCZG+RwYoMAAAAAEDg8/sio62tTXl5ebLb7f0+v3z5ci1cuFC33367Nm3apLy8PM2ePVvV1dV9r1m7dq02btyol156SX/84x/18ccfn674hvt8awkrMgAAAAAAgc/vi4w5c+bo97//vS677LJ+n7/vvvs0f/58zZs3T7m5uVqyZImioqK0dOnSvtdkZmZKkgYPHqyvf/3r2rRpU7/3cjgcam5uPuIR6FiRAQAAAAAIJn5fZBxPV1eXNm7cqFmzZvVdM5vNmjVrltatWyepZ0VHS0uLJKm1tVXvvPOOxo0b1+/97rrrLsXHx/c9srKyBv6HGGAJh2dkNDHsEwAAAAAQBAK6yKitrZXL5VJ6evoR19PT01VZWSlJqqqq0jnnnKO8vDydddZZuu6661RQUNDv/W655RY1NTX1PSoqKgb8ZxhovUVGA8M+AQAAAABBIOiPXx0+fLi2bNlyQq+NiIhQRETEACc6vRJ6Z2SwtQQAAAAAEAQCekVGSkqKLBaLqqqqjrheVVWlQYMGGZTKv/TOyGiiyAAAAAAABIGALjLCw8M1efJkrVy5su+a2+3WypUrNW3atJO+r91uV25u7jG3oASS3hUZLQ6nul1ug9MAAAAAAHBq/H5rSWtrq3bt2tX3eVlZmTZv3qykpCRlZ2dr4cKFmjt3rqZMmaKpU6dq0aJFamtr07x58076exYVFamoqEjNzc2Kj4/3xY9hmDjb57/FzR3dSo4Jrq0zAAAAAIDQ4vdFxoYNGzRz5sy+zxcuXChJmjt3rpYtW6Yrr7xSNTU1uu2221RZWan8/Hy9/vrrRw0ADVVWi1mxNqtaOp1qpMgAAAAAAAQ4vy8yzj//fHk8nuO+ZsGCBVqwYMFpShR4EqPCe4oMTi4BAAAAAAS4gJ6RMVCCaUaG9PkRrJxcAgAAAAAIdBQZ/SgqKlJpaalKSkqMjuIT8ZEUGQAAAACA4ECREQJ6Ty5p7KDIAAAAAAAENoqMEJBweEVGEzMyAAAAAAABjiIjBPTOyGhgawkAAAAAIMBRZPQj+IZ9srUEAAAAABAcKDL6EWzDPhP6hn2ytQQAAAAAENgoMkJA79aSJlZkAAAAAAACHEVGCOgtMjh+FQAAAAAQ6CgyQkB85OEZGWwtAQAAAAAEOIqMEJB4eEVGc6dTTpfb4DQAAAAAAJw8iox+BNupJfGHh31KPWUGAAAAAACBiiKjH8F2aonVYlZshFUS20sAAAAAAIGNIiNExPcO/OTkEgAAAABAAKPICBF9R7BycgkAAAAAIIBRZISIhMMnlzSwtQQAAAAAEMAoMkJE74qMRlZkAAAAAAACGEVGiEhgRgYAAAAAIAhQZPQj2I5flT7fWtLE1hIAAAAAQACjyOhHsB2/KrEiAwAAAAAQHCgyQkR8ZE+R0cCMDAAAAABAAKPICBGJUWwtAQAAAAAEPoqMEMHWEgAAAABAMKDICBEcvwoAAAAACAYUGSEi/vCpJc2d3XK5PQanAQAAAADg5FBkhIjeYZ8ej9TSyaoMAAAAAEBgosgIEeFWs6LDLZI4uQQAAAAAELgoMvpht9uVm5urgoICo6P4VMLhk0saObkEAAAAABCgKDL6UVRUpNLSUpWUlBgdxac4uQQAAAAAEOgoMkJIb5HRxNYSAAAAAECAosgIIQmRbC0BAAAAAAQ2iowQEn94RQbDPgEAAAAAgYoiI4Qk9m4tYUYGAAAAACBAUWSEELaWAAAAAAACHUVGCInn1BIAAAAAQICjyAgh0eFWSVJ7l8vgJAAAAAAAnByKjBBiC+v57e7spsgAAAAAAAQmiowQEhlmkUSRAQAAAAAIXBQZ/bDb7crNzVVBQYHRUXzKFt5TZHRQZAAAAAAAAhRFRj+KiopUWlqqkpISo6P4VO+KjI4ut8FJAAAAAAA4ORQZIcTG1hIAAAAAQICjyAghzMgAAAAAAAQ6iowQ0ltkON0edbvYXgIAAAAACDwUGSEkIuzz324GfgIAAAAAAhFFRgiJsJplMvV8zPYSAAAAAEAgosgIISaT6fM5GZxcAgAAAAAIQBQZIabvCFZWZAAAAAAAAhBFRoixUWQAAAAAAAIYRUaIsR0e+MmMDAAAAABAIKLICDGR4azIAAAAAAAELoqMEPP5sE+KDAAAAABA4KHICDG9MzI6nRQZAAAAAIDAQ5ERYvqGfXL8KgAAAAAgAFFk9MNutys3N1cFBQVGR/E5jl8FAAAAAAQyiox+FBUVqbS0VCUlJUZH8TlOLQEAAAAABDKKjBDTN+yTIgMAAAAAEIAoMkKMrff4VU4tAQAAAAAEIIqMEMOMDAAAAABAIKPICDE2igwAAAAAQACjyAgxvSsyHN0cvwoAAAAACDwUGSGGrSUAAAAAgEBGkRFiIg4fv8qwTwAAAABAIKLICDF9x686KTIAAAAAAIGHIiPERHL8KgAAAAAggFFkhJi+FRnMyAAAAAAABCCKjBDD8asAAAAAgEBGkRFibH0rMjh+FQAAAAAQeCgyQkzfjAxWZAAAAAAAAhBFRojpnZHR5XTL5fYYnAYAAAAAAO9QZIQYW9jnv+UOjmAFAAAAAAQYiowQY7Na+j7mCFYAAAAAQKChyAgxZrNJEdae33bmZAAAAAAAAg1FRgj6/OQSigwAAAAAQGChyAhBkRzBCgAAAAAIUCFRZLS3t2vo0KG66aabjI7iFziCFQAAAAAQqEKiyPjDH/6gs846y+gYfqN3awnDPgEAAAAAgSboi4ydO3dq27ZtmjNnjtFR/EbvEazMyAAAAAAABBq/LjJWr16tiy66SBkZGTKZTFqxYsVRr7Hb7crJyZHNZlNhYaGKi4uPeP6mm27SXXfddZoSB4beGRlsLQEAAAAABBq/LjLa2tqUl5cnu93e7/PLly/XwoULdfvtt2vTpk3Ky8vT7NmzVV1dLUl68cUXNXr0aI0ePfp0xvZ7kZxaAgAAAAAIUFajAxzPnDlzjrsl5L777tP8+fM1b948SdKSJUv06quvaunSpbr55pu1fv16Pf3003r22WfV2tqq7u5uxcXF6bbbbuv3fg6HQw6Ho+/z5uZm3/5AfsIWzowMAAAAAEBg8usVGcfT1dWljRs3atasWX3XzGazZs2apXXr1kmS7rrrLlVUVGjv3r363//9X82fP/+YJUbv6+Pj4/seWVlZA/5zGMFmPbwiw8nxqwAAAACAwBKwRUZtba1cLpfS09OPuJ6enq7KysqTuuctt9yipqamvkdFRYUvovqdyPCe33ZWZAAAAAAAAo1fby3xpe9973tf+pqIiAhFREQMfBiDMSMDAAAAABCoAnZFRkpKiiwWi6qqqo64XlVVpUGDBhmUKjDYKDIAAAAAAAEqYIuM8PBwTZ48WStXruy75na7tXLlSk2bNu2U7m2325Wbm6uCgoJTjemXbBy/CgAAAAAIUH69taS1tVW7du3q+7ysrEybN29WUlKSsrOztXDhQs2dO1dTpkzR1KlTtWjRIrW1tfWdYnKyioqKVFRUpObmZsXHx5/qj+F3IvuKDIZ9AgAAAAACi18XGRs2bNDMmTP7Pl+4cKEkae7cuVq2bJmuvPJK1dTU6LbbblNlZaXy8/P1+uuvHzUAFEeK5PhVAAAAAECA8usi4/zzz5fH4znuaxYsWKAFCxacpkTBwRbWs6PI4aTIAAAAAAAEloCdkTGQgn1GRt/WElZkAAAAAAACDEVGP4qKilRaWqqSkhKjowwIhn0CAAAAAAIVRUYIiuT4VQAAAABAgKLICEG2viKDU0sAAAAAAIGFIiME9Z1awooMAAAAAECAocgIQQz7BAAAAAAEKoqMfgT7qSURh49f7XS6vvR4WwAAAAAA/AlFRj+C/dSS3hUZHo/kcDInAwAAAAAQOCgyQlDvsE+Jk0sAAAAAAIGFIiMEhVnMCrOYJHFyCQAAAAAgsFBkhCiblZNLAAAAAACBhyKjH8E+7FOSbOGcXAIAAAAACDwUGf0I9mGf0heOYGVFBgAAAAAggFBkhCjb4SNYHRQZAAAAAIAAQpERoliRAQAAAAAIRBQZIcpGkQEAAAAACEAUGSEq8vCwT45fBQAAAAAEEoqMEMXxqwAAAACAQESR0Y9QOH61b0UGx68CAAAAAAIIRUY/QuH4VWZkAAAAAAACEUVGiOo9taSTIgMAAAAAEEAoMkKULaznt54VGQAAAACAQEKREaJYkQEAAAAACEQUGSGK41cBAAAAAIGIIiNERfQO++TUEgAAAABAAKHICFGRnFoCAAAAAAhAFBkhiiIDAAAAABCIKDL6YbfblZubq4KCAqOjDJjI8J7fegdFBgAAAAAggFBk9KOoqEilpaUqKSkxOsqAsVlZkQEAAAAACDwUGSHKFk6RAQAAAAAIPBQZIap3RgbHrwIAAAAAAglFRojqKzI4fhUAAAAAEEAoMkKUjVNLAAAAAAABiCIjRPWuyHC6Pep2sb0EAAAAABAYKDJClC3889/6TlZlAAAAAAACBEVGiAq3mGUy9XzM9hIAAAAAQKCgyAhRJpPpCwM/2VoCAAAAAAgMFBkhrK/IcLIiAwAAAAAQGCgy+mG325Wbm6uCggKjowyovpNLOIIVAAAAABAgKDL6UVRUpNLSUpWUlBgdZUDZwnp++5mRAQAAAAAIFBQZISwq3CpJau9yGpwEAAAAAIATQ5ERwpKiwyVJda1dBicBAAAAAODEUGSEsJSYCElSTavD4CQAAAAAAJwYiowQlhp7uMhoocgAAAAAAAQGiowQ1ltk1LK1BAAAAAAQICgyQlhKTM+MjJqWToOTAAAAAABwYigyQhgrMgAAAAAAgYYiI4SlMSMDAAAAABBgKDJCWO+pJU0d3XI4XQanAQAAAADgy1FkhLD4yDCFWUySpDq2lwAAAAAAAgBFRggzmUx9qzLYXgIAAAAACAQUGSEulTkZAAAAAIAAQpER4lJjek8uocgAAAAAAPg/iox+2O125ebmqqCgwOgoA46tJQAAAACAQEKR0Y+ioiKVlpaqpKTE6CgDrm9rCSsyAAAAAAABgCIjxPUWGWwtAQAAAAAEAoqMEMfWEgAAAABAIKHICHGfr8joMjgJAAAAAABfjiIjxHH8KgAAAAAgkFBkhLiUmHBJUqvDqY4ul8FpAAAAAAA4PoqMEBcTYZUtrOcfAwZ+AgAAAAD8HUVGiDOZTH0DP6vZXgIAAAAA8HMUGWBOBgAAAAAgYFBkQKkxvSeXUGQAAAAAAPwbRQaUwooMAAAAAECAoMgAKzIAAAAAAAGDIgPMyAAAAAAABAyKDPSdWlLDigwAAAAAgJ+jyEDfigy2lgAAAAAA/B1FBpT2ha0lHo/H4DQAAAAAABwbRQb6tpZ0drvV6nAanAYAAAAAgGOjyIAiwy2KibBKkmpbuwxOAwAAAADAsVFkQJKUEhMuiZNLAAAAAAD+jSIDkjiCFQAAAAAQGIK+yGhsbNSUKVOUn5+v8ePH68EHHzQ6kl/i5BIAAAAAQCCwGh1goMXGxmr16tWKiopSW1ubxo8fr8svv1zJyclGR/MrvQM/WZEBAAAAAPBnQb8iw2KxKCoqSpLkcPQcL8oRo0dLjWFFBgAAAADA//l9kbF69WpddNFFysjIkMlk0ooVK456jd1uV05Ojmw2mwoLC1VcXHzE842NjcrLy9OQIUP0y1/+UikpKacpfeBgRgYAAAAAIBD4fZHR1tamvLw82e32fp9fvny5Fi5cqNtvv12bNm1SXl6eZs+ererq6r7XJCQkaMuWLSorK9OTTz6pqqqq0xU/YPRtLWFFBgAAAADAj/l9kTFnzhz9/ve/12WXXdbv8/fdd5/mz5+vefPmKTc3V0uWLFFUVJSWLl161GvT09OVl5enNWvW9Hsvh8Oh5ubmIx6hIjMxUpJUVtMml5utNwAAAAAA/+T3RcbxdHV1aePGjZo1a1bfNbPZrFmzZmndunWSpKqqKrW0tEiSmpqatHr1ao0ZM6bf+911112Kj4/ve2RlZQ38D+EnRqXFKCrcohaHU7uqW42OAwAAAABAvwK6yKitrZXL5VJ6evoR19PT01VZWSlJKi8v14wZM5SXl6cZM2boZz/7mSZMmNDv/W655RY1NTX1PSoqKgb8Z/AXVotZ+VkJkqSN5Q3GhgEAAAAA4BiC/vjVqVOnavPmzSf02oiICEVERAxsID82KTtRH+yu06Z9DbqmMNvoOAAAAAAAHCWgV2SkpKTIYrEcNbyzqqpKgwYNMihV4Jo0NEGStGkfKzIAAAAAAP4poIuM8PBwTZ48WStXruy75na7tXLlSk2bNu2k72u325Wbm6uCggJfxAwYE7MSJUl7atrU0NZlcBoAAAAAAI7m90VGa2urNm/e3Lc9pKysTJs3b9a+ffskSQsXLtSDDz6oRx99VJ999pl+8pOfqK2tTfPmzTvp71lUVKTS0lKVlJT44kcIGInR4RqeGi1J+qiCVRkAAAAAAP/j9zMyNmzYoJkzZ/Z9vnDhQknS3LlztWzZMl155ZWqqanRbbfdpsrKSuXn5+v1118/agAoTszk7ETtqWnTxvIGXTCW/w0BAAAAAP7F5PF4PEaH8FfNzc2Kj49XU1OT4uLijI5zWjxVvE+3PP+Jpg1P1lM/PMvoOAAAAACAEODN+2+/31qC02tSds+cjC37G+V0uQ1OAwAAAADAkSgy+hGqwz4laVRajGIjrGrvcmlbZYvRcQAAAAAAOAJFRj9CddinJJnNJuVnJ0iSPuIYVgAAAACAn6HIwFF6t5ds2tdobBAAAAAAAP4DRQaOMnloT5GxsZwVGQAAAAAA/0KRgaPkZyfIZJL21berttVhdBwAAAAAAPpQZPQjlId9SlKcLUyj02IlsSoDAAAAAOBfKDL6EcrDPntNGBIvSdp2iJNLAAAAAAD+gyID/RqRGiNJ2lPbanASAAAAAAA+R5GBfo1IjZYk7a6hyAAAAAAA+A+KDPRreO+KjJo2eTweg9MAAAAAANCDIgP9GpocJavZpPYulyqbO42OAwAAAACAJIqMfoX6qSWSFGYxKzspSlLPqgwAAAAAAPwBRUY/OLWkR+/2EuZkAAAAAAD8BUUGjql34CcrMgAAAAAA/oIiA8c0ghUZAAAAAAA/Q5GBYxrOigwAAAAAgJ+hyMAx9a7IONDYofYup8FpAAAAAACgyMBxJEaHKzEqTJJUVsuqDAAAAACA8Sgy+sHxq5/7/OQSigwAAAAAgPEoMvrB8auf+/zkEgZ+AgAAAACMR5GB42JFBgAAAADAn1Bk4Lh6B36yIgMAAAAA4A8oMnBcXzyC1e32GJwGAAAAABDqKDJwXNlJUbKaTerodqmyudPoOAAAAACAEEeRgeMKs5iVnRwlqWdVBgAAAAAARqLIwJca0TfwkzkZAAAAAABjUWT0w263Kzc3VwUFBUZH8QvDOYIVAAAAAOAnKDL6UVRUpNLSUpWUlBgdxS+M4AhWAAAAAICfoMjAlxpxeEUGW0sAAAAAAEajyMCXGpkWK5NJOtTUqSpOLgEAAAAAGIgiA18qPjJMEzLjJUlrdtYanAYAAAAAEMooMnBCZoxKkSSt3VljcBIAAAAAQCijyMAJmTEqVZK0dlet3G6PwWkAAAAAAKHqlIqMzk7mJYSKSdmJigq3qLa1S59VNhsdBwAAAAAQorwuMtxut+68805lZmYqJiZGe/bskSTdeuutevjhh30eEP4h3GrWtOHJkpiTAQAAAAAwjtdFxu9//3stW7ZMd999t8LDw/uujx8/Xg899JBPw8G/nNM3J4MiAwAAAABgDK+LjMcee0wPPPCAvvvd78pisfRdz8vL07Zt23waDv6ld05G8d56dXS5DE4DAAAAAAhFXhcZBw4c0MiRI4+67na71d3d7ZNQRrPb7crNzVVBQYHRUfzKiNRoZcTb1OV0q3hvvdFxAAAAAAAhyOsiIzc3V2vWrDnq+nPPPaeJEyf6JJTRioqKVFpaqpKSEqOj+BWTydS3KmPNDo5hBQAAAACcflZvv+C2227T3LlzdeDAAbndbj3//PPavn27HnvsMb3yyisDkRF+ZMboFC3fUKG1u5iTAQAAAAA4/bxekXHJJZfo5Zdf1ttvv63o6Gjddttt+uyzz/Tyyy/rwgsvHIiM8CNnj0iRySRtq2xRdTPH7wIAAAAATi+vV2RI0owZM/TWW2/5OgsCQGJ0uCZkxuvj/U16o7RK15411OhIAAAAAIAQ4vWKDODCM9IlSb97aaueLt5ncBoAAAAAQCjxusgwm82yWCzHfCD4/fC84bokP0NOt0c3P/+J/vBqqVxuj9GxAAAAAAAhwOutJS+88MIRn3d3d+ujjz7So48+qt/97nc+Cwb/FWG1aNGV+RqeEqO/vL1DD64p0776dv3tu5NlMZuMjgcAAAAACGImj8fjk79Kf/LJJ7V8+XK9+OKLvridX2hublZ8fLyampoUFxdndBy/9NKWg7rp2S3qcrp197fO1HcKsoyOBAAAAAAIMN68//bZjIyzzjpLK1eu9NXtECAuzsvQL786RpJ09xvb1epwGpwIAAAAABDMfFJkdHR06P7771dmZqYvbocAM3d6jnKSo1Tb6tDf3t1ldBwAAAAAQBDzekZGYmKiTKbP5yB4PB61tLQoKipKjz/+uE/DITCEW8369TdyNf+xDXpobZmunpqtrKQoo2MBAAAAAIKQ10XGX/7ylyOKDLPZrNTUVBUWFioxMdGn4RA4Zp2RprNHJuv9XXX607+3yf7dSUZHAgAAAAAEIZ8N+wxGDPv0zmeHmvWN+9fI7ZGW//AsFQ5PNjoSAAAAACAAePP++4RWZHz88ccn/M3PPPPME34tgssZg+N01dRsPfnhPj24powiAwAAAADgcydUZOTn58tkMunLFm+YTCa5XC6fBENgurqgp8j4cE+dnC63rBafHYwDAAAAAMCJFRllZWUDncOv2O122e12SpmTkJsRpzibVc2dTm092Ky8rASjIwEAAAAAgggzMo6DGRknZ/5jG/RWaZV+9bWx+sn5I4yOAwAAAADwcz6fkdGf0tJS7du3T11dXUdcv/jii0/2lggS00ck663SKq3bU0eRAQAAAADwKa+LjD179uiyyy7TJ598csTcjN4jWdmOgWkjeoZ8lpTVq8vpVriVORkAAAAAAN/w+h3mjTfeqGHDhqm6ulpRUVHaunWrVq9erSlTpmjVqlUDEBGBZnRarJKjw9XR7dLH+xuNjgMAAAAACCJeFxnr1q3THXfcoZSUFJnNZpnNZp1zzjm66667dMMNNwxERgQYs9mksw4fvfrB7jqD0wAAAAAAgonXRYbL5VJsbKwkKSUlRQcPHpQkDR06VNu3b/dtOgSs3u0l6ygyAAAAAAA+5PWMjPHjx2vLli0aNmyYCgsLdffddys8PFwPPPCAhg8fPhAZEYB6i4yN+xrU2e2SLcxicCIAAAAAQDDwekXGb37zG7ndbknSHXfcobKyMs2YMUOvvfaa7r//fp8HRGAanhKt9LgIdTnd2lTeYHQcAAAAAECQ8HpFxuzZs/s+HjlypLZt26b6+nolJib2nVwCmEwmTRuerBWbD2rdnjpNH5lidCQAAAAAQBDwekXG448/rra2tiOuJSUlUWLgKNNH9JQXDPwEAAAAAPiK10XGL37xC6Wnp+uaa67Ra6+9JpfLNRC5EAR652RsqWhUm8NpcBoAAAAAQDDwusg4dOiQnn76aZlMJn3nO9/R4MGDVVRUpA8++GAg8iGAZSVFKTMhUk63R1v2NxodBwAAAAAQBLwuMqxWq775zW/qiSeeUHV1tf7yl79o7969mjlzpkaMGDEQGRHAxgzqOap3T03bl7wSAAAAAIAv5/Wwzy+KiorS7Nmz1dDQoPLycn322We+yoUgMTwlWu+IIgMAAAAA4Bter8iQpPb2dj3xxBP6+te/rszMTC1atEiXXXaZtm7d6ut8CHDDU2MkSXtqWw1OAgAAAAAIBl6vyLjqqqv0yiuvKCoqSt/5znd06623atq0aQORDUFgeGq0JFZkAAAAAAB8w+siw2Kx6JlnntHs2bNlsVgGIhOCSG+Rsb+hXQ6nSxFW/pkBAAAAAJw8r4uMJ554YiByIEilxkQoNsKqFodT5XXtGp0ea3QkAAAAAEAAO6kZGcCJMplMX9hewpwMAAAAAMCpCfoio6KiQueff75yc3N15pln6tlnnzU6UsjpHfi5mzkZAAAAAIBTdErHrwYCq9WqRYsWKT8/X5WVlZo8ebK+/vWvKzo62uhoIWN4CgM/AQAAAAC+EfRFxuDBgzV48GBJ0qBBg5SSkqL6+nqKjNOII1gBAAAAAL5yUltL3G63duzYobVr12r16tVHPHxt9erVuuiii5SRkSGTyaQVK1Yc9Rq73a6cnBzZbDYVFhaquLi433tt3LhRLpdLWVlZPs+JY/viEawej8fgNAAAAACAQOb1ioz169frmmuuUXl5+VFvSk0mk1wul8/CSVJbW5vy8vJ0/fXX6/LLLz/q+eXLl2vhwoVasmSJCgsLtWjRIs2ePVvbt29XWlpa3+vq6+t13XXX6cEHH/RpPny5YYe3ljR1dKu+rUvJMREGJwIAAAAABCqvi4wf//jHmjJlil599VUNHjxYJpNpIHL1mTNnjubMmXPM5++77z7Nnz9f8+bNkyQtWbJEr776qpYuXaqbb75ZkuRwOHTppZfq5ptv1vTp0495L4fDIYfD0fd5c3Ozj36K0GYLsygzIVIHGju0p7aNIgMAAAAAcNK83lqyc+dO/fGPf9QZZ5yhhIQExcfHH/E4nbq6urRx40bNmjWr75rZbNasWbO0bt06SZLH49H3vvc9XXDBBbr22muPe7+77rrriJ+FLSi+wxGsAAAAAABf8LrIKCws1K5duwYii9dqa2vlcrmUnp5+xPX09HRVVlZKkt5//30tX75cK1asUH5+vvLz8/XJJ5/0e79bbrlFTU1NfY+KiooB/xlCBSeXAAAAAAB8weutJT/72c/03//936qsrNSECRMUFhZ2xPNnnnmmz8L5wjnnnCO3231Cr42IiFBEBNseBkLvySW7KTIAAAAAAKfA6yLjW9/6liTp+uuv77tmMpnk8XgGZNjn8aSkpMhisaiqquqI61VVVRo0aNBpy4Ev17e1hCNYAQAAAACnwOsio6ysbCBynJTw8HBNnjxZK1eu1KWXXiqp52jYlStXasGCBSd9X7vdLrvdflpLmWDXuyJjX127ul1uhVlO6uRfAAAAAECI87rIGDp06EDkOKbW1tYjZnKUlZVp8+bNSkpKUnZ2thYuXKi5c+dqypQpmjp1qhYtWqS2tra+U0xORlFRkYqKitTc3HzaB5gGq8FxNtnCzOrsdquivr2v2AAAAAAAwBteFxmStHv3bi1atEifffaZJCk3N1c33nijRowY4dNwkrRhwwbNnDmz7/OFCxdKkubOnatly5bpyiuvVE1NjW677TZVVlYqPz9fr7/++lEDQGEss9mkYSkx+uxQs/bUtFFkAAAAAABOitdFxhtvvKGLL75Y+fn5OvvssyX1nAwybtw4vfzyy7rwwgt9GvD888+Xx+M57msWLFhwSltJcHoMT43uKTJqWyVRNAEAAAAAvOd1kXHzzTfrF7/4hf70pz8ddf1Xv/qVz4sMBI8RHMEKAAAAADhFXk9c/Oyzz/T973//qOvXX3+9SktLfRLKaHa7Xbm5uSooKDA6SlDp3U7yxtZK/XN9uRxOhqkCAAAAALzjdZGRmpqqzZs3H3V98+bNSktL80UmwxUVFam0tFQlJSVGRwkq00ckKzMhUg3t3bp1xaeaec8qPfJ+mT490KSOLkoNAAAAAMCX83pryfz58/XDH/5Qe/bs0fTp0yX1zMj485//3DeIE+hPWpxNK//7PC0vqdDfVu3SwaZO/e7lz1fxDEmMVEZCpBIiw5QQFabEqHBlJERqSGKkspKilJ0UJVuYxcCfAAAAAABgNJPnyyZp/gePx6NFixbp3nvv1cGDByVJGRkZ+uUvf6kbbrhBJpNpQIIaoff41aamJsXFxRkdJ6h0dru0vKRCr35ySLurW1XX1vWlXxNmMWlCZrym5CRpytBEnTs6lWIDAAAAAIKAN++/vS4yvqilpUWSFBsbe7K38GsUGadPXatDu6pbVdPqUGN7t5o6ulXX2qUDje2qqO9QRUO7WjqdR3xNQlSYrpySpf86a6iykqIMSg4AAAAAOFWnrcgIVna7XXa7XS6XSzt27KDI8AMej0cV9R0q2VuvDeUNWr2jRgcaOyRJJpN0/uhUXZyfoVlnpCvWFmZwWgAAAACAN3xeZEyaNEkrV65UYmKiJk6ceNztI5s2bfI+sZ9iRYb/crk9WrW9Wss+2Ks1O2v7rodbzTp/dKqumpqlmWPSgmqrEwAAAAAEK2/ef5/QsM9LLrlEERERfR/z5hBGs5hN+soZ6frKGenaU9OqFR8d0CsfH9Ke2ja9WVqlN0urNCY9Vj86b7guystQmMXrA3oAAAAAAH6IrSXHwYqMwOLxePTZoRY9v2m/ni6pUKujZ6ZGZkKkfnL+CF0xZYgirAwHBQAAAAB/M6AzMoYPH66SkhIlJycfcb2xsVGTJk3Snj17vE/spygyAldTR7ceX1+uR97fq9pWhyQpI96mn84cSaEBAAAAAH5mQIsMs9msyspKpaWlHXG9qqpKWVlZ6ur68mM0AwVFRuDrPeb1b6t2qaq5p9CIDrdo+sgUnTc6VeeNTtWQxEi2SwEAAACAgXw+I0OSXnrppb6P33jjDcXHx/d97nK5tHLlSg0bNuwk4vqfL55agsBmC7No7vQcXVmQpeUlFVry3m4daurUW6VVequ0SpI0KM6myTmJmpydqCk5iTpjcBwzNQAAAADAT53wigyzueeNnclk0n9+SVhYmHJycnTvvffqm9/8pu9TGoQVGcHH7fao9FCz3ttRo/e212jjvga53Ef+8xwZZlFeVrymDE3SpRMzNTItxqC0AAAAABAaBnRrybBhw1RSUqKUlJRTChkIKDKCX3uXU1sqmrSxvF4byxu0sbxBzZ3OI15zwdg0/eCcYZo2IpktKAAAAAAwAAa0yAglFBmhx+32aHdNqzaUN2jlZ1Vaua1avf+GjEmP1SUTM3TRmRnKSooyNigAAAAABJEBLTJuuOEGjRw5UjfccMMR1xcvXqxdu3Zp0aJFXgf2VxQZKKtt0yPvl+nZDfvV0f35zJRJ2Qn6yhnpmj4iWRMy42VlpgYAAAAAnLQBLTIyMzP10ksvafLkyUdc37Rpky6++GLt37/f+8R+iiIDvZrau/Xap4f00uaDWl9Wpy/+WxMbYdX0kcm6qiBb545OlcXM9hMAAAAA8MaAnFrSq66u7ogTS3rFxcWptrbW29sBASE+KkxXT83W1VOzVd3cqde3Vur9XbVat7tOzZ1OvbG1Sm9srVJWUqS+WzhUl0/MVFqczejYAAAAABB0vC4yRo4cqddff10LFiw44vq///1vDR8+3GfBjMTxqzietDibrpuWo+um5cjl9mjrwSa9uPmgnt1QoYr6Dv3p39v0p39v04TMeM0ck6rzxqTpzCHxHOkKAAAAAD7g9daSpUuXasGCBfrlL3+pCy64QJK0cuVK3XvvvVq0aJHmz58/IEGNwNYSeKOjy6WXtxzUk8X7tLmi8YjnbGFm5Q1J0JScRF2cl6kxg2KNCQkAAAAAfmjATy35+9//rj/84Q86ePCgJCknJ0e//e1vdd11151cYj9FkYGTVdPi0Hs7avTutmq9v7tWje3dfc/Zwsx64gdnafLQRAMTAgAAAID/OG3Hr9bU1CgyMlIxMTEnewu/RpEBX3C7PdpT26oNexv0r037VbK3QQlRYXrux9M1Mi04/90BAAAAAG948/77lDbtp6amBm2JAfiK2WzSyLRYXTU1W49eP1V5WQlqbO/W3KXFqm7uNDoeAAAAAAQUr4uMqqoqXXvttcrIyJDVapXFYjniAeDYosKtWjp3ioalROtAY4fmPlKiHVUtOoWFUQAAAAAQUrzeWjJnzhzt27dPCxYs0ODBg2UymY54/pJLLvFpQCOxtQQDZV9duy7/+/uqbe2SJKXFRuickSk6a3iyJgyJ16i0GFk55QQAAABAiBjQGRmxsbFas2aN8vPzTyVjQKDIwEDaVtmsP762TR/uqZPD6T7iuQirWWcMjtOUoYmaNiJZU4clKdYWZlBSAAAAABhY3rz/tnp786ysrKBfBm+322W32+VyuYyOgiA2dlCcHrt+qjq7XdpY3qC1u2q1qbxBWw82q9Xh1OaKRm2uaNRDa8tkNkmThyZq3tnDNHvcIFnMpi//BgAAAAAQhLxekfHmm2/q3nvv1T/+8Q/l5OQMUCz/wIoMGMHt9qi8vl1bKhr1YVmdPthdp/K69r7nh6VEa/6M4fra+EFKjAo7ansXAAAAAASaAd1akpiYqPb2djmdTkVFRSks7Mjl7vX19d4n9lMUGfAXBxo7tLx4nx5dV66mju6+67E2q3KSozUsJVpnDI7TuIyeR3JMhIFpAQAAAMA7A1pkPProo8d9fu7cud7czq9RZMDftDmceqp4nx5fX669X1il8Z/OGp6k/70iT0MSo05jOgAAAAA4OQNaZIQSigz4s85ul/bVt2tvbZt21bRq68FmlR5sVlltmyQpzmbVn791puZMGGxwUgAAAAA4vgEtMvbt23fc57Ozs725nV+jyEAg2lfXrhue/kibKxolSVcVZGnOhMHKiLcpIyFS0RFez/gFAAAAgAE1oEWG2Ww+7nDBYDrpgyIDgarb5dZ9b+3Qkvd26z//DY+zWZWREHn4YdPg+EhlJkRqcLxNYwfFKT6KY14BAAAAnF4DevzqRx99dMTn3d3d+uijj3TffffpD3/4g7e3AzAAwixm/eprY3XOyBQ98n6ZKuo7dLCpQy2dTjV3OtVc2aJtlS1HfV1MhFWPXj9Vk4cmGpAaAAAAAL6cz2ZkvPrqq7rnnnu0atUqX9zOL7AiA8GmpbNbh5o6dbCxQwcbO3WoqUMHGjt0qLFTZbVtqmzuVGJUmP71k+kanhpjdFwAAAAAIWJAV2Qcy5gxY1RSUuKr2wEYALG2MMXawjQ6Pfao59q7nLr6gfXasr9J33ukRM//dLpSOMYVAAAAgJ8xe/sFzc3NRzyampq0bds2/eY3v9GoUaMGIiOA0yAq3KqH5hYoOylK++rb9f1lJWpzOI2OBQAAAABH8MmwT4/Ho6ysLD399NOaNm2aTwMawW63y263y+VyaceOHWwtQUjZU9Oqb/39AzW0dyvCatYZg+M0PjNOBTlJuujMDJnNxx72CwAAAAAnY0BPLXnvvfeO+NxsNis1NVUjR46U1RpcxzoyIwOhamN5g37y+EZVtziOuH5hbroWXZnPEa4AAAAAfMrnRcakSZO0cuVKJSYm6o477tBNN92kqKgonwX2VxQZCGVut0fl9e365ECTtlQ06p/ry9XldGvsoFg9NHeKhiQG/38DAAAAAJwePi8yIiMjtXPnTg0ZMkQWi0WVlZVKTU31WWB/RZEBfG7Tvgb98LGNqm11KDk6XIuvmaRpI5KNjgUAAAAgCPi8yJg2bZpiYmJ0zjnn6He/+51uuukmxcT0fzTjbbfddnKp/RBFBnCkg40d+sGjG1R6qFmSNHtcuv7na2M1gqNaAQAAAJwCnxcZ27dv1+23367du3dr06ZNys3N7Xcehslk0qZNm04+uZ+hyACO1t7l1O9f/UxPF++T2yNZzCZdVZCl/5k9VvFRYUbHAwAAABCABnTYp9lsVmVlpdLS0k4pZCCgyACObWdVi/78+ja9/Vm1JCkzIVL3X52vyUOTDE4GAAAAINAMaJERSigygC+3fk+dfvWvj1Ve1y6L2aRfzBqln5w/UhaOaQUAAABwgigyfIQiAzgxLZ3dunXFp1qx+aAkKT0uQikxEYqzhSk+MkyD4m3KSLApIyFSuYPjNJyZGgAAAAC+wJv330cPugAAL8XawvSXK/N1zqhU3fbip6pqdqiq2XHM188ck6ofnTdChcOSZDKxcgMAAADAiWNFxnGwIgPwXmN7l/bUtqm5o1vNnU41tnfpUFOnDjZ2qKK+XZsrGuU+/F+dvKwE3fqNMzQlh7kaAAAAQChja4mPUGQAvre3tk0Prd2jZzfsl8PpVpjFpN9fOl5XFmQbHQ0AAACAQbx5/2329uYVFRXav39/3+fFxcX6+c9/rgceeMD7pABCTk5KtH5/6QS9f/MF+saEwep2efSrf32iO18pldPlNjoeAAAAAD/ndZFxzTXX6N1335UkVVZW6sILL1RxcbF+/etf64477vB5QADBKSUmQouvmahfzBotSXp4bZnmLSvR3to2g5MBAAAA8GdeFxmffvqppk6dKkl65plnNH78eH3wwQd64okntGzZMl/nAxDETCaTbpw1SvZrJskWZtaanbX6yn3v6ZfPbtG+unaj4wEAAADwQ14XGd3d3YqIiJAkvf3227r44oslSWPHjtWhQ4d8mw5ASPjGmYP1YtE5umBsmlxuj57duF8X3LtK9725XYzxAQAAAPBFXhcZ48aN05IlS7RmzRq99dZb+trXviZJOnjwoJKTk30e0Ah2u125ubkqKCgwOgoQMsYMitXS7xXohZ9O17mjU+V0e3T/O7t09xuUGQAAAAA+5/WpJatWrdJll12m5uZmzZ07V0uXLpUk/b//9/+0bds2Pf/88wMS1AicWgIY55/r9urWF7dKkm78yij94sLRBicCAAAAMFAG/PhVl8ul5uZmJSYm9l3bu3evoqKilJaW5n1iP0WRARjr4bVluvOVUknSL2ePUdHMkQYnAgAAADAQBvT41Y6ODjkcjr4So7y8XIsWLdL27duDqsQAYLzvnzNMt8wZK0m6543tenHzAYMTAQAAADCa10XGJZdcoscee0yS1NjYqMLCQt1777269NJL9fe//93nAQGEth+dN0JFM0dIkm57cauqWzoNTgQAAADASF4XGZs2bdKMGTMkSc8995zS09NVXl6uxx57TPfff7/PAwLAz2eN1riMODV1dOvWFZ8y/BMAAAAIYV4XGe3t7YqNjZUkvfnmm7r88stlNpt11llnqby83OcBASDMYtY9386T1WzSG1ur9MrHHPUMAAAAhCqvi4yRI0dqxYoVqqio0BtvvKGvfvWrkqTq6moGYgIYMLkZcfrp4WGft7+0VXWtDoMTAQAAADCC10XGbbfdpptuukk5OTmaOnWqpk2bJqlndcbEiRN9HhAAei2YOVJjB8Wqvq1L/++FT+R2s8UEAAAACDUndfxqZWWlDh06pLy8PJnNPV1IcXGx4uLiNHbsWJ+HNArHrwL+55P9Tbrsb+/L6fbomsJs/eHS8TKZTEbHAgAAAHAKvHn/fVJFRq/9+/dLkoYMGXKyt/BrFBmAf3px8wH9fPlmeTzSvLNzdNs3cykzAAAAgADmzftvr7eWuN1u3XHHHYqPj9fQoUM1dOhQJSQk6M4775Tb7T7p0ABwoi7Jz9Sfv3WmJOmR9/fq7je2c5IJAAAAECKs3n7Br3/9az388MP605/+pLPPPluStHbtWv32t79VZ2en/vCHP/g8JAD8p+9MyZLD6datKz7V31ftlscj/eprY1iZAQAAAAQ5r7eWZGRkaMmSJbr44ouPuP7iiy/qpz/9qQ4cOODTgEZiawng/x5eW6Y7XymVJF05JUt/uGy8rBavF5sBAAAAMNCAbi2pr6/vd6Dn2LFjVV9f7+3tAOCUfP+cYfrT5RNkNknLN1So6MlN6ux2GR0LAAAAwADxusjIy8vT4sWLj7q+ePFi5eXl+SQUAHjjqqnZ+tt3JyncYtYbW6v0g0c3yMXRrAAAAEBQ8npGxt13361vfOMbevvttzVt2jRJ0rp161RRUaHXXnvN5wEB4ER8bfxgLbs+TD94dIPW7qrVS1sO6LKJwXmiEgAAABDKvF6Rcd5552nHjh267LLL1NjYqMbGRl1++eXavn27ZsyYMRAZAeCETB+RoqKZIyVJ9721Q11OTlICAAAAgo3Xwz6PZf/+/brjjjv0wAMP+OJ2foFhn0Dgae9y6ty7V6m21aE7Lxmna6flGB0JAAAAwJcY0GGfx1JXV6eHH37YV7cDgJMSFW7VDV/pWZVx/zu71NHF4E8AAAAgmITEGYWXXXaZEhMT9e1vf9voKABOg6sKsjUkMVI1LQ4t+2Cv0XEAAAAA+FBIFBk33nijHnvsMaNjADhNwq1m/WLWaEnSkvd2q6mj2+BEAAAAAHwlJIqM888/X7GxsUbHAHAaXToxU6PSYtTU0a15jxTr3W3VcnMkKwAAABDwTvj41csvv/y4zzc2Np5qln6tXr1a99xzjzZu3KhDhw7phRde0KWXXnrEa+x2u+655x5VVlYqLy9Pf/3rXzV16tQByQMgMFjMJt1+0Thdv6xEm/Y1at6yEo1Ki9H3zs7RBWPTNDg+0uiIAAAAAE7CCRcZ8fHxX/r8ddddd8qB/lNbW5vy8vJ0/fXX91umLF++XAsXLtSSJUtUWFioRYsWafbs2dq+fbvS0tJ8ngdA4DhnVIre+5/z9cj7e/Xkh/u0s7pVv37hU0nSyLQYzRiVoq/mDlLhsCSZzSaD0wIAAAA4ET47fvV0MJlMR63IKCwsVEFBgRYvXixJcrvdysrK0s9+9jPdfPPNfa9btWqVFi9erOeee+6Y93c4HHI4HH2fNzc3Kysri+NXgSDQ3Nmt5cUVevWTQ/p4f6O+uMskI96mi/Mz9a1JmRqVzjY0AAAA4HQz5PhVI3R1dWnjxo2aNWtW3zWz2axZs2Zp3bp1Xt/vrrvuUnx8fN8jKyvLl3EBGCjOFqb55w7XiqKz9dGtX9XfvztJ35kyRLE2qw42dWrJe7t14V9Wa+Ezm1Xd0ml0XAAAAADHENBFRm1trVwul9LT04+4np6ersrKyr7PZ82apSuuuEKvvfaahgwZcsyS45ZbblFTU1Pfo6KiYkDzAzBGfFSY5kwYrLu/naeSX8/S3787SbPOSJfJJD2/6YAu+N/39NCaPep2uY2OCgAAAOA/nPCMjED29ttvn9DrIiIiFBERMcBpAPgTW5hFcyYM1pwJg7W5olG3v/iptuxv0u9f/UyPvL9XV0wZoiumZCkzgeGgAAAAgD8I6BUZKSkpslgsqqqqOuJ6VVWVBg0aZFAqAIEqPytBL/z0bP35WxOUHB2uA40dWvT2Tp3z53c0d2mxtlU2Gx0RAAAACHkBXWSEh4dr8uTJWrlyZd81t9utlStXatq0aSd9X7vdrtzcXBUUFPgiJoAAYjabdGVBtt6/+QL931X5mjY8WR6P9N6OGl381/f14Oo9crsDZkYyAAAAEHT8/tSS1tZW7dq1S5I0ceJE3XfffZo5c6aSkpKUnZ2t5cuXa+7cufrHP/6hqVOnatGiRXrmmWe0bdu2o2ZneMubqakAgtfe2jbd8Uqp3tlWLUk6a3iS7v1OPttNAAAAAB/x5v233xcZq1at0syZM4+6PnfuXC1btkyStHjxYt1zzz2qrKxUfn6+7r//fhUWFp7y96bIANDL4/HoqeIK3flKqTq6XYoKt+gHM4brh+cOV0xESIwbAgAAAAZMUBUZRqLIAPCf9ta26aZnt2hDeYMkKSUmXDd+ZZSunpotqyWgd+sBAAAAhqHI8BGKDAD98Xg8+venlbr79W3aW9cuSSrISdTiayYpPc5mcDoAAAAg8Hjz/pu/PuwHwz4BHI/JZNLXJwzWWwvP0x2XjFNshFUlexv0jfvXaN3uOqPjAQAAAEGNFRnHwYoMACeirLZNP3l8o7ZVtshskm6eM1Y/PHeE0bEAAACAgMGKDAA4jYalROuFn56tyydlyu2R/vjaNr2zrcroWAAAAEBQosgAAB+IDLfo3ivyNO/sHEnSHS+XyuF0GRsKAAAACEIUGQDgIyaTSf/91TFKjY3Q3rp2PbSmzOhIAAAAQNChyOgHwz4BnKyYCKtumTNWkrT4nV061NRhcCIAAAAguDDs8zgY9gngZHg8Hn17yTptLG/QRXkZ+uvVE42OBAAAAPg1hn0CgIFMJpN+d/E4mUzSy1sO6o2tlaIzBgAAAHyDIgMABsD4zHhdPTVbkvSjf27UrPvek/3dXTrYyFYTAAAA4FRQZADAALllzlh9e/IQ2cLM2l3Tpnve2K5z735Xf359mzq7OdEEAAAAOBnMyDgOZmQA8IWWzm79+9NKPbdhv4r31kuScpKj9MfLJmj6yBSD0wEAAADGY0bGKeLUEgC+FGsL03emZOmZH0/TP66drEFxNu2ta9c1D32oO18pZX4GAAAA4AVWZBwHKzIADISWzm7d/fp2Pf5huTwe6YavjNLCC0cbHQsAAAAwDCsyAMCPxdrCdOel43XnJeMlSfev3Kl/ri83OBUAAAAQGCgyAMAg/3XWUP181ihJ0m0vfqrXPjlkcCIAAADA/1FkAICBbvzKKF1TmC2PR/r505v1b8oMAAAA4LgoMgDAQCaTSXdeMl5zxg9Sl8utnzyxSfe8sU0uN+OLAAAAgP5QZACAwSxmk/569UR9/5xhkiT7u7v1/UdL1NTRbXAyAAAAwP9QZPSD41cBnG5Wi1m3fjNXi67MV4TVrFXba3T5395XTYvD6GgAAACAX+H41ePg+FUARvj0QJPmP7ZBh5o6NXZQrJ6af5YSo8ONjgUAAAAMGI5fBYAANj4zXk/NP0tpsRHaVtmi65YWq7mTbSYAAACARJEBAH4pJyVaT84vVHJ0uD450KR5j5So1eE0OhYAAABgOIoMAPBTI9Ni9c/vFyrOZtXG8gZd/Ne1+nh/o9GxAAAAAENRZACAH8vNiNPjPyjUoDib9tS26fK/fSD7u7s4nhUAAAAhiyIDAPzcmUMS9PrPZ+gbEwbL6fbonje266oH1ml7ZYvR0QAAAIDTjiIDAAJAQlS4Fl8zUf97RZ6iwy0q2dugr9+/Rne8XMogUAAAAIQUiox+2O125ebmqqCgwOgoANDHZDLp25OH6I1fnKs54wfJ5fZo6ftluuB/V+kf7+1WbavD6IgAAADAgDN5PB42Wh+DN+fYAsDptmZnjW5/aav21LRJkqxmky7MTdd3C4fqnFEpBqcDAAAATpw3778pMo6DIgOAv+tyuvXCR/v1VHGFNlc09l2/OC9Dd14yXvFRYcaFAwAAAE4QRYaPUGQACCSfHWrWkx/u05PF++RyezQozqZ7rjhTM0alGh0NAAAAOC5v3n8zIwMAgsQZg+N056Xj9dyPp2lYSrQqmzt17cPFuuu1z+TmuFYAAAAECYoMAAgyE7MT9eoN5+i6aUMlSf9YvUc/X75ZXU63wckAAACAU0eRAQBBKCrcqjsuGa9FV+bLajbppS0Hdf2yErU6nEZHAwAAAE4JRQYABLFLJ2bq4e8VKCrcorW7anXFknV6eG2ZPtxTR6kBAACAgMSwz+Ng2CeAYLGlolHXLytRXVtX3zWTSZqdO0iLrsqXLcxiYDoAAACEOoZ9AgCOkJeVoJd+do4WXjhaF+amKyPeJo9Hen1rpeY/tkGd3S6jIwIAAAAnhBUZx8GKDADB7MM9dZq3rETtXS6dNzpV/7h2MiszAAAAYAhWZJwiu92u3NxcFRQUGB0FAAZM4fBkPfK9AkWGWfTejhr95PGN2t/QbnQsAAAA4LhYkXEcrMgAEArW7a7TvGXF6uzuOZ41I96mgmFJmjkmTV+fMFjhVjpvAAAADCxv3n9TZBwHRQaAUPHhnjr96fVt+mR/k5zuz/9vYVCcTd8/Z5iuLsxWTITVwIQAAAAIZhQZPkKRASDUtHc59dG+Rq3fU6flJRWqbnFIkmJtVt32zVxdMSXL4IQAAAAIRhQZPkKRASCUOZwurfjogP6xeo/21LRJkm64YKR+ceFomUwmg9MBAAAgmDDsEwBwyiKsFl1ZkK23f3GeFswcKUm6/51d+u9ntqjL6TY4HQAAAEIVRQYA4LjMZpNumj1Gf7p8gixmk57/6IDmLSuWw+kyOhoAAABCEEUGAOCEXDU1Ww/PnaLocIve31WnRW/vNDoSAAAAQhBFBgDghJ0/Jk33XZkvSfrHe7u1sbzB2EAAAAAIORQZAACvzB43SJdPypTbI9307BZ1dLHFBAAAAKcPRQYAwGu3XzROg+JsKqtt059f32Z0HAAAAIQQigwAgNfiI8N097fPlCQt+2Cv1u6sNTgRAAAAQgVFBgDgpJw7OlXfLcyWJF2/rEQPrN4tt9tjcCoAAAAEO4oMAMBJ+/U3ztCFuenqcrn1x9e26ZqH1utAY4fRsQAAABDEKDIAACctKtyqB66drD9dPkGRYRat31Ovry1arTe3VhodDQAAAEGKIgMAcEpMJpOumpqt126cobysBLV0OvXDf27Un1/fJqfLbXQ8AAAABBmKjH7Y7Xbl5uaqoKDA6CgAEDCGpUTruR9P07yzcyRJf1+1W9ctLVZtq8PYYAAAAAgqJo/Hw2S2Y2hublZ8fLyampoUFxdndBwACBgvbzmoX/3rY7V3uZSVFKknf3CWspKijI4FAAAAP+XN+29WZAAAfO6ivAy9tOBsDU2OUkV9h656YL3K69qMjgUAAIAgQJEBABgQI9NitfyH0zQ8JVoHGjt05T/Wq6yWMgMAAACnhq0lx8HWEgA4ddUtnfrugx9qZ3WrUmIi9LXx6cpOilJ2UpRGp8dqWEq0TCaT0TEBAABgIG/ef1NkHAdFBgD4Rm2rQ//10IfaVtly1HOpsREqHJakwuHJmjY8SSNSYyg2AAAAQgxFho9QZACA77Q6nPr3J4dUXteuffXtKq9r02eVLepyHnlEa0pMuKYOS9J5o1N1SX6mbGEWgxIDAADgdKHI8BGKDAAYWJ3dLm2uaNSHe+q1fk+dNu1rkOMLxUZSdLiuPWuorps2VMkxEQYmBQAAwECiyPARigwAOL0cTpc+3t+kD3bV6dmNFdrf0CFJirCa9aPzRuiGC0bKamFONQAAQLChyPARigwAMI7T5dbrWyv1wOo9+nh/kyRp8tBELboyX1lJUQanAwAAgC958/6bv9YCAPglq8Wsb56ZoReLztb9V09UbIRVG8sb9PX712jFRwfkctPDAwAAhCJWZBwHKzIAwH9U1Lfrxqc/0qZ9jZJ6Tjv5xoTBujg/QxOzEjjpBAAAIICxtcRHKDIAwL84XW4tfneXHnl/r5o6uvuu5yRH6YopWfrWpCEaFG8zMCEAAABOBkWGj1BkAIB/6nK6tWZnjV7aclBvlVapvcslSTKbpBmjUjVjVIoKhyXrjMGxDAcFAAAIABQZPkKRAQD+r83h1GufHNKzG/areG/9Ec/FRFg1LiNOI9NiNDItRqPSYjUxO0HREVaD0gIAAKA/FBk+QpEBAIFlT02r3iytUklZvYr31qul03nUa8IsJk3KTtSMUSmaMSpV4zPjZTEzXwMAAMBIFBk+QpEBAIHL5fZoe2WLtlU2a1d1q3ZVt2rrwWYdaOw44nXxkWE6e2SyZoxK1YW56UqJiTAoMQAAQOiiyPARigwACC4ej0flde1as7NGa3bWat3uOrU4Pl+1YTWb9JUz0nRlQZbOHZXKfA0AAIDThCLDRygyACC4OV1ubdnfpLU7a7VyW5U+3t/U91xmQqR+f9l4zRyTZmBCAACA0ECR4SMUGQAQWrZVNuuZkv164aP9amjvOd71islD9Jtv5io+MszgdAAAAMGLIsNHKDIAIDR1dLn0v29u19L3y+TxSIPibLrvyjxNH5FidDQAAICg5M3776Df/PvKK69ozJgxGjVqlB566CGj4wAAAkBkuEW3fjNXz/xomoalRKuyuVPzHilRyX8c7woAAIDTL6hXZDidTuXm5urdd99VfHy8Jk+erA8++EDJyckn9PWsyAAAdHS5VPTkJr2zrVqxNquW/3CacjP4/wQAAABfYkXGYcXFxRo3bpwyMzMVExOjOXPm6M033zQ6FgAggESGW2S/ZpIKchLV0unUdUuLVV7XZnQsAACAkOXXRcbq1at10UUXKSMjQyaTSStWrDjqNXa7XTk5ObLZbCosLFRxcXHfcwcPHlRmZmbf55mZmTpw4MDpiA4ACCKR4RY9NLdAZwyOU22rQ//18Icqq6XMAAAAMIJfFxltbW3Ky8uT3W7v9/nly5dr4cKFuv3227Vp0ybl5eVp9uzZqq6uPqnv53A41NzcfMQDAABJio8M06PXF2hocpQq6jv0zfvX6MXNlOMAAACnm18XGXPmzNHvf/97XXbZZf0+f99992n+/PmaN2+ecnNztWTJEkVFRWnp0qWSpIyMjCNWYBw4cEAZGRnH/H533XWX4uPj+x5ZWVm+/YEAAAEtLdamZ340TVOHJamty6Ubn96sW57/WB1dLqOjAQAAhAy/LjKOp6urSxs3btSsWbP6rpnNZs2aNUvr1q2TJE2dOlWffvqpDhw4oNbWVv373//W7Nmzj3nPW265RU1NTX2PioqKAf85AACBJT3Opid/UKgbLhgpk0l6qrhCc/5vtd7cWqkgnp8NAADgN6xGBzhZtbW1crlcSk9PP+J6enq6tm3bJkmyWq269957NXPmTLndbv3P//zPcU8siYiIUERExIDmBgAEPqvFrIVfHaPC4cn6xfLN2lvXrh/+c6MKhyXp1m/manxmvNERAQAAglbArsg4URdffLF27NihXbt26Yc//KHRcQAAQeTskSl656bztWDmSEVYzfqwrF4XLV6rW1d8qlaH0+h4AAAAQSlgi4yUlBRZLBZVVVUdcb2qqkqDBg06pXvb7Xbl5uaqoKDglO4DAAh+MRFW3TR7jN656Xxdkp8hj0f65/pyffW+9/Tu9pMbPg0AAIBjC9giIzw8XJMnT9bKlSv7rrndbq1cuVLTpk07pXsXFRWptLRUJSUlpxoTABAiMhMi9X9XTdQTPyhUVlKkDjZ1at4jJZq7tFj2d3dp7c5aNXV0Gx0TAAAg4Pn1jIzW1lbt2rWr7/OysjJt3rxZSUlJys7O1sKFCzV37lxNmTJFU6dO1aJFi9TW1qZ58+YZmBoAEMrOHpmiN35+ru57c4eWvl+m93bU6L0dNZIkk0maPiJZ35mSpdnjBskWZjE4LQAAQOAxefx4xPqqVas0c+bMo67PnTtXy5YtkyQtXrxY99xzjyorK5Wfn6/7779fhYWFPvn+zc3Nio+PV1NTk+Li4nxyTwBA6Nhe2aL3dlRry/4mfbK/Sfvq2/uei48M0xWTh+jGWaMUawszMCUAAIDxvHn/7ddFhtEoMgAAvlRR367nNu7XsxsqdLCpU1LPlpR7rjhT00ekGJwOAADAOBQZp8hut8tut8vlcmnHjh0UGQAAn3K5PXp3W7V++/JW7W/okCR9b3qO/vuro1mdAQAAQhJFho+wIgMAMJBaHU798bXP9OSH+yRJ4RazzhqRrAvPSNOs3HQNjo80OCEAAMDpQZHhIxQZAIDT4b0dNbrj5a3aXdPWd81skr41qWeGxpDEKAPTAQAADDyKDB+hyAAAnE67qlv19mdVequ0ShvLGyRJYRaTrpmarQUXjFJqbITBCQEAAAYGRYaPUGQAAIzy0b4G3fvmDq3dVStJSomJ0CPfK9CEIfEGJwMAAPA9b95/m09TpoBit9uVm5urgoICo6MAAELUxOxEPf6DQj05v1Cj02NU2+rQd/6xTu9sqzI6GgAAgKFYkXEcrMgAAPiDls5u/fSJTVqzs1Zmk3TnpeP13cKhRscCAADwGVZkAAAQRGJtYVr6vQJ9e/IQuT3Sr1/4VI9+sNfoWAAAAIagyAAAIACEWcy659tnasHMkZKk3728Vat31BicCgAA4PSjyAAAIECYTCb991dH61uTelZmFD25SbuqW42OBQAAcFpRZAAAEEBMJpP+ePl4TRmaqJZOp77/aIka2rqMjgUAAHDaUGT0g1NLAAD+LMJq0T+unawhiZEqr2vXdx/6UG9urZTLzfxuAAAQ/Di15Dg4tQQA4M+2V7bo20s+UEunU5KUnRSl703P0dVTsxUZbjE4HQAAwInz5v03RcZxUGQAAPxdZVOnHl23V09+uE9NHd2SpMHxNv3qa2N1cV6GzGaTwQkBAAC+HEWGj1BkAAACRUeXS89/tF9/X7Vb+xs6JEn5WQm66atjNCUnUbYwVmgAAAD/RZHhIxQZAIBA09nt0sNry/S3d3eprcslSQqzmJSbEa9J2Qn6+oTBmjI0USYTKzUAAID/oMjwEYoMAECgqm7u1F/e3qm3SitV23rkqSZnDI7TddOG6pL8DEWFWw1KCAAA8DmKDB+hyAAABDqPx6P9DR3atK9Ba3bW6pWPD6qz2y1JSooO15+/daYuzE03OCUAAAh1FBmnyG63y263y+VyaceOHRQZAICg0dTerWc3VuixdeXaV98uSbr+7GG6ec5YhVs5lR0AABiDIsNHWJEBAAhWXU637n59mx5aWyZJOnNIvOzXTFJWUpTByQAAQCjy5v03f/UCAEAICrea9Ztv5uqh66YoPjJMH+9v0uV//0C7a1qNjgYAAHBcFBkAAISwWbnpeu3GGRo7KFY1LQ5d9cB6ygwAAODXKDIAAAhxmQmRenL+WZQZAAAgIFBkAAAAJUWHH1VmbCyvNzoWAADAUSgyAACApJ4y44kfFPaVGVcsWae7XvtMnd0uo6MBAAD0ocgAAAB9kmMitPxH03T5pEy5PdI/Vu/RN/+6ltUZAADAb1Bk9MNutys3N1cFBQVGRwEA4LSLjwzTfd/J1wPXTlZKTIR2VbfqW39fp6seWKdV26vFye0AAMBIJg9/Gjkmb86xBQAgGNW3demu1z7TCx8dkNPd80eGsYNiNXd6ji7Ky1BMhNXghAAAIBh48/6bIuM4KDIAAOhxoLFDS9eW6anifWrv6pmZER1u0SUTM3XN1GyNz4w3OCEAAAhkFBk+QpEBAMCRmtq79cyGCj1VvE97atv6rp85JF5XT81mlQYAADgpFBk+QpEBAED/PB6P1u+p15PF+/TGp5Xqcrkl9azSuDg/Q1cVZOvMIfEymUwGJwUAAIGAIsNHKDIAAPhyda0OPb/pwFGrNMYOitXF+RkanhKtIYlRykqMUnxUmIFJAQCAv6LI8BGKDAAATlzvKo3lJfv02qeV6nK6j3pNrM2qIYlRGpIYqZzkKE3MTtSUoYlKi7MZkBgAAPgLigwfocgAAODkNLV3a8XmAyrZW6/9DR3a39Cu2tauY74+OylKZ49M1qwz0nX2yBTZwiynMS0AADAaRYaPUGQAAOA7HV0uHWhsV0V9T7Gxo6pVG8obtK2yWV/800hkmEXnjk7RZRMzdcHYdIVbzcaFBgAApwVFho9QZAAAMPCaO7u1sbxB726r1tulVTrY1Nn3XFJ0uC7Nz9TVU7M0Kj3WwJQAAGAgUWT4CEUGAACnl8fjUemhZr285ZCe37Rf1S2OvucuGJumH583QgU5iZyGAgBAkKHIOEV2u112u10ul0s7duygyAAAwABOl1urd9boqeIKvf1ZVd/2k0nZCbpiSpa+mpuu5JgIY0MCAACfoMjwEVZkAADgH8pq2/TA6j3616b9faehmE1SQU6SvnnmYF0yMVNxNo52BQAgUFFk+AhFBgAA/qW6pVPPbtiv1z+t1CcHmvquR4ZZdOnEDH23cKjGZ8YbmBAAAJwMigwfocgAAMB/VdS3642tlXpmQ4V2VLX2XT9nZIpunjOWQgMAgABCkeEjFBkAAPg/j8ej4rJ6Pf7hPr3+6SF1u3r+aHNxXoZ+OXuMspKiDE4IAAC+DEWGj1BkAAAQWCrq23Xvm9u1YvNBSVK4xazfXTJOV0/NNjgZAAA4Hm/ef5tPUyYAAIABl5UUpUVXTdQrPztH00ckq8vl1i3Pf6JfPfexOrtdRscDAAA+QJEBAACCzvjMeD3+/UL9cvYYmU3S8g0V+s4/1ulAY4fR0QAAwCmiyAAAAEHJbDapaOZIPXr9VCVEhenj/U26ZPH7+vQLp50AAIDAQ5EBAACC2oxRqXp5wTkaOyhWta0OXfmPdVq9o8boWAAA4CRRZAAAgKCXlRSlZ348TdNHJKuty6Xrl5Xo+U37jY4FAABOAkUGAAAICXG2MC2bN1WX5GfI6fZo4TNbdMNTH6mivt3oaAAAwAsUGQAAIGSEW836y3fy9ePzRshkkl7aclBfufc9/eHVUjW2dxkdDwAAnACKDAAAEFLMZpNunjNWLy84R2eP7Dmi9cE1ZZrzf2u0t7bN6HgAAOBLUGQAAICQ1HtE6yPzCpSTHKVDTZ26+sH1lBkAAPg5igwAABCyTCaTZo5J07M/nq6RaTGUGQAABACKjH7Y7Xbl5uaqoKDA6CgAAOA0SI2N0FPzzzqizNhXxxBQAAD8kcnj8XiMDuGvmpubFR8fr6amJsXFxRkdBwAADLCaFoeufnC9dlW3akRqtJ7/6dmKjwwzOhYAAEHPm/ffrMgAAAA4LDU2Qk/+oFCD423aXdOmBU9uktPlNjoWAAD4AooMAACAL0iLs+mhuVMUFW7Rmp21+t3LpUZHAgAAX0CRAQAA8B/GZcRr0ZX5Mpmkf64v16Mf7DU6EgAAOIwiAwAAoB9fHTdIv/raWEnS717eqvd21BicCAAASBQZAAAAx/Sjc4frislD5PZIC57YpJ1VLUZHAgAg5FFkAAAAHIPJZNIfLpugqTlJanE49f1HN6i+rcvoWAAAhDSKDAAAgOMIt5q15NrJyk6K0r76dv34nxvlcLqMjgUAQMiiyAAAAPgSSdHhenjuFMVGWFW8t17zHilRRX270bEAAAhJFBkAAAAnYFR6rOzfnSRbmFkf7K7T1xat1j/Xl8vt9hgdDQCAkEKRAQAAcILOHZ2qf994rgpyEtXW5dKtKz7Vfz38oZrau42OBgBAyKDIAAAA8MKwlGgt/+E03X5Rbt/qjOseKVZLJ2UGAACnA0UGAACAl8xmk+adPUwv/PRsJUSFaUtFo+Y9UqI2h9PoaAAABD2KDAAAgJN0xuA4/fP6QsXarNpQ3qAfPLpBnd2caAIAwECiyAAAADgFE4bE69Hrpyo63KJ1e+p0qf19/fuTQwwBBQBggFBkAAAAnKJJ2Yl6ZN5Uxdqs2lbZop88sUlf+7/VemnLQXW73EbHAwAgqJg8Hg9/XXAMzc3Nio+PV1NTk+Li4oyOAwAA/FxDW5eWvl+mZe/vVcvheRnpcRG6siBbVxVkKSMh0uCEAAD4J2/ef1NkHAdFBgAAOBlNHd1a9v5e/XP9XtW2dkmSzCbpwtx0/ei8EZqUnWhwQgAA/AtFho9QZAAAgFPR5XTrzdJKPbF+n9btqeu7PnVYkn583nCdNzpNFrPJwIQAAPgHigwfocgAAAC+srOqRQ+s3qMVmw+o29Xzx6/U2Ah9Y8JgXZQ3WBOzEmWm1AAAhCiKjP9w2WWXadWqVfrKV76i55577oS/jiIDAAD4WmVTp5a+X6blJRVq6ujuu56VFKmrCrJ1xeQhSouzGZgQAIDTjyLjP6xatUotLS169NFHKTIAAIBf6HK6tXZXjV7eckhvbq1UW5dLkmQxm/SVsWkqmjlSeVkJxoYEAOA08eb9t/U0ZTLU+eefr1WrVhkdAwAAoE+41awLxqbrgrHp6uhy6bVPDump4n3aUN6gN0ur9NZnVbqqIEu/nD1WSdHhRscFAMBvmI0OsHr1al100UXKyMiQyWTSihUrjnqN3W5XTk6ObDabCgsLVVxcfPqDAgAADJDIcIu+NXmInvvJdL35i3N12cRMeTzSU8UVmvm/q/TP9eVyu4N+ES0AACfE8CKjra1NeXl5stvt/T6/fPlyLVy4ULfffrs2bdqkvLw8zZ49W9XV1X2vyc/P1/jx4496HDx40KssDodDzc3NRzwAAABOp9HpsfrLlfl69sfTNHZQrJo6unXrik91xT/WaVd1i9HxAAAwnF/NyDCZTHrhhRd06aWX9l0rLCxUQUGBFi9eLElyu93KysrSz372M918880nfO9Vq1Zp8eLFx52R8dvf/la/+93vjrrOjAwAAGAEp8utx9eX6543tquty6Vwi1k/u2Ckvj9jmKLCQ2KHMAAgRATNjIyuri5t3LhRt9xyS981s9msWbNmad26dT7/frfccosWLlzY93lzc7OysrJ8/n0AAABOhNVi1vfOHqYLxw3Sb174RO9ur9G9b+3QfW/v0NCkKI1Oj9WItBilxUYoOSZCKTHhSo3p+TghMozjXAEAQcmvi4za2lq5XC6lp6cfcT09PV3btm074fvMmjVLW7ZsUVtbm4YMGaJnn31W06ZNO+p1ERERioiIOOXcAAAAvpSZEKml3yvQS1sO6s//3qaDTZ3aW9euvXXtUmlVv19jMZuUFB2ulMMFR0pMhOIjwxQdYVFUuFVR4RZFh1sVFXH41/DD1w9/bgszyxZmUYTVLJOJQgQA4D/8usjwlbffftvoCAAAAKfEZDLpkvxMXZKfqdpWh7ZXtmh7ZYv21rWprrVLNa0O1bU6VNvapaaObrncHtW0OFTT4jjl7x1h7Sk1esuNhMgwpcZGKCUmQmlxNo1Jj9XYwbHKSY6WhVUgAIAB5tdFRkpKiiwWi6qqjvybhqqqKg0aNGjAvq/dbpfdbpfL5Rqw7wEAAHCyUmIilDIyQmePTOn3+S6nW/VtXaptdai21aG61p6Pmzu71eZwqaPLpbYup9q7XGpzHP61y9lz3eFUW5dLri+ckuJwuuVwutXU0fN5+TFy2cLMOnNIgs4dlaIZo1I1PjOeYgMA4HMBMexz6tSp+utf/yqpZ9hndna2FixY4NWwz5PhzbARAACAYNLtcquz2yWHs+fXzu7eX11qaO9WbWvPao8DDR3aVtms7VUt6ux2H3GPxKgwzRybptnjBuncUamKDLcY9NMAAPxdQA37bG1t1a5du/o+Lysr0+bNm5WUlKTs7GwtXLhQc+fO1ZQpUzR16lQtWrRIbW1tmjdvnoGpAQAAgluYxawwi1mxJ/h6l9ujstpWrd9TrzU7a/TBrjo1tHfr+U0H9PymA4oMs2jaiGRNzEpQXlaC8oYkKD4qbEB/BgBAcDJ8RcaqVas0c+bMo67PnTtXy5YtkyQtXrxY99xzjyorK5Wfn6/7779fhYWFA56NFRkAAAAnx+lya2N5g97YWqU3tlbqQGPHUa+JDLMoLtKqOFuY4iPDFBd5+Feb9Qsfhyku0qqocGvfoNLo/xhKyjBSAAh83rz/NrzI8GcUGQAAAKfO4/Fo68FmfVhWry0Vjdqyv1Hlde0+ubfJpL5TV6Ij+j+Npe96hFWxNqtiIqyKtYUd/rX30VOcMNMDAIwRUFtL/BHDPgEAAHzHZDJpfGa8xmfG911r6exWY3u3mjq61dzRrebO3o+dX/i4W82dTjV3dKuty6X2LqfaHD2/tnf1/DnN45FaHU61OpzSKZ7QYjJJCZFhSowOV3xkmGJtYYq19awYiftC4ZEUHa7B8TYNircpLdamcKv5lL4vAMA7rMg4DlZkAAAA+Ce326OO7sOnrzj6OYXlC6extDtcanU41Xa48Gh1ONXc6VRrZ7daHU61dH5ejHjLZJIyEyI1PDVGw1OiNWZQrCYPTdTI1BiZWd0BACeMFRkAAAAIamazSdERVkVHWHXCE0mPo8vpVmNHlxraulXf1qXmzm61HF4N0tLpVEvv553dqmvt0qHmDlU1OdTlcmt/Q4f2N3Ro9Y6avvvFR4ZpUnaCclKiNSiuZ/VGRkKkRqXFKCEq/NQDA0AIo8gAAABAyAu3mpUW27NV5ER5PB7VtnaprLZNe2patae2TZ/sb9JHFQ1q6ujWu9trpO01R31delyERqfHasrQJF0wNk3jMuJYvQEAXmBryXGwtQQAAADe6na5VXqwWZsrGnWwqUPVzQ5VNnVqX317v6e3pMVG6CtnpOm/zhqqcRnx/dwRAIIfp5acoi8O+9yxYwdFBgAAAHyipbNbO6tbVXqwWWt21mjNztoj5nNMH5Gs+ecO1/mjUzlWFkBIocjwEVZkAAAAYCA5nC59uKdez23cr1c/OSSXu+eP5uMz43TnJeM1MTvR4IQAcHpQZPgIRQYAAABOlwONHVr2fpmeKq5Qq8Mpk0m6emq2/mf2GAaEAgh6FBk+QpEBAACA06221aG7Xtumf23aL0lKig7XfxVm69uTs5SdHGVwOgAYGBQZPkKRAQAAAKN8uKdOv1nxqXZWt/ZdmzosSbPHDdKI1GjlJEdrSGKkrBazgSkBwDcoMnyEIgMAAABG6na59e9PK/Xshgqt3VWr//yTe5jFpPGZ8SoclqzC4UkqyElSTITVmLAAcAooMk4Rp5YAAADA3xxq6tALHx3Q5n2NKq9r1966Njmc7iNeE24168Iz0nX5pEydOzpVYazWABAgKDJ8hBUZAAAA8Fdut0f7GzpUvLdeH+6p0/qyOlXUd/Q9nxwdrssnZeqawqEalhJtYFIA+HIUGT5CkQEAAIBA4fF4VHqoWc9vOqAXNx9QbWtX33PnjEzR1VOzde7oFMXawgxMCQD9o8jwEYoMAAAABKJul1vvba/REx+Wa9WOmr7ZGlazSROzEzRjVKpmnZGu3Az+jAvAP1Bk+AhFBgAAAAJdRX27nirep9c+OaS9de1HPJc7OE5XTBmiS/IzlRQdblBCAKDI8BmKDAAAAASTivp2rdlZq1Xbq7Vqe426XD3DQs0maVCcTZmJkRqSGKWc5GiNGRSrMwbHKisxSmazyeDkAIIdRYaPUGQAAAAgWDW0demlLQf17MYKfXqg+Ziviwq3aPLQRE0fkaLpI5I1PjNeFooNAD5GkXGKOH4VAAAAoaSmxaH9De3a39Ch/Q0d2l3Tqm2VzdpR1aqu/zjiNSk6XJfkZ+jbk4doXEa8QYkBBBuKDB9hRQYAAABCmdPl1s7qVq3fU6cPdtdp/Z46tXQ6+57PHRynudOH6rKJQxRuNRuYFECgo8jwEYoMAAAA4HNOl1trdtbquY379VZpVd+MjcyESP34vOG6YkqWbGEWg1MCCEQUGT5CkQEAAAD0r6GtS89t3K8H1uxRTYtDkpQWG6FfXDhaV0weIquFFRoAThxFho9QZAAAAADH19nt0jMbKrRk1W4dbOqUJI1Ki9HNc8bqgrFpMpkYDArgy1Fk+AhFBgAAAHBiHE6XHl+/T399Z6ca27slSVlJkcobkqAzh8RrfEa8RqbHKDUmgnIDwFEoMnyEIgMAAADwTlNHt/62apceeX/vUSeeSFKszaoRqTE9j7Tovo+HpURzrCsQwigyfIQiAwAAADg5TR3d+mR/kz4+0KhP9jdp68Fm7W9ol/sY7z6iwy2aMCReeUMSNDE7UWcNT1JCVPjpDQ3AMBQZp8hut8tut8vlcmnHjh0UGQAAAIAPdHa7VF7Xrt01rdpd3drza02bdlW3qqPbdcRrTSZpXEacpo9IUX5WgsZlxCk7KYptKUCQosjwEVZkAAAAAAPP5fZoV3WrtlQ0avP+RhWX1WtXdetRr4uNsGrCkHidPTJF545K1biMOJnZjgIEBYoMH6HIAAAAAIxR3dypD3bXaf2eOn16sEk7KlvV5Tpy5kZiVFhfqXHOqBRlJEQalBbAqaLI8BGKDAAAAMA/dLvc2lnVqo3l9Vq9s1brdtep1eE84jUj02J0YW66vjZukM4cEs82FCCAUGT4CEUGAAAA4J+6XW5trmjUmp21WrOzRlsqGo8YJDo43qaL8jL03cJsDU2ONi4ogBNCkeEjFBkAAABAYGhq79aqHdV6c2uV3t1erfaunuGhJpN0/uhUXTc9R+eNSmWmBuCnKDJ8hCIDAAAACDyd3S69t6NGT364T+/tqOm7PiotRj88d7guyc9UuNVsYEIA/4kiw0coMgAAAIDAVlbbpsfXl+uZkgq1HJ6pMSjOpvnnDtd3C7NlC7MYnBCARJHhMxQZAAAAQHBo7uzWkx/u09K1ZapucUjqmaPx81mj9K1JQ2S1sEIDMBJFho9QZAAAAADBxeF06V8bD+iv7+zUoaZOSdLw1Gjd9NUxmjN+ECedAAahyPARigwAAAAgOHV2u/T4+nLZ392lhvZuSdKEzHj9cvYYzRiVQqEBnGYUGafIbrfLbrfL5XJpx44dFBkAAABAkGrp7NZDa8r00Jo9ajt80knhsCR9/5xh+soZ6bJwyglwWlBk+AgrMgAAAIDQUNfqkP3d3Xp8fbm6XG5JUmZCpP7rrKG6JD9DGQmRBicEghtFho9QZAAAAACh5WBjh/65vlxPF+/r23IiSTnJUZo+MkXnjEzRuaNTFRNhNTAlEHwoMnyEIgMAAAAITZ3dLr285aCeLqnQR/sa5P7Cu6Zwq1nnjkrR7HGDdMbgONnCzIqwWhRhNSsi7PCvVjNzNgAvUGT4CEUGAAAAgObObhXvqdf7u2v17rZq7a1rP6Gv6y00IsIsR5Qdti+UHZ9/fPg1YRbZDn9NZJhFkeEWDYqzaVhKtIYkRnJMLIIWRYaPUGQAAAAA+CKPx6MdVa16/dNKvf1ZlWpaHOp0uuTodqvT6dJAvruymk3KTo7S8JRoDUuJ1rCUGOWkRGlYSrTSY20yM5gUAYwiw0coMgAAAACcKI/Ho26XRw6nSw6nW53dPb/2lhyObrccTpc6D//a89zhX7/w+s5ulzq7Xerodqvd4dSBxg7trWtTZ7f7mN/bFmZWTnK0cpKjNTQlSsOSo5VzuPBIi41gmwv8njfvv5lQAwAAAAA+YDKZFG41KdxqVqyP7+12e1TZ3Kmy2jbtqW1TWU2b9tS2am9tmyoaOtTZ7da2yhZtq2w56mujwi3KSY7WmUPiNSUnSVOGJmpochTlBgIWKzKOgxUZAAAAAPxdt8utAw0dKqtr097ankdZXbv21rZpf0P7EYNKe6XFRuj8Mam6YGyazhnFKSwwHltLfIQiAwAAAEAg63K6tb+hXbuqW7VpX6M27K3Xx/ub1OX6fJtKmMWkvCEJfas18rMTlBwdzooNnFYUGT5CkQEAAAAg2HR2u1Syt17vbKs+5iks4Vaz0uMilB5rU3ZylMakx2p0eqzGDIrV4HgbJQd8jiLDRygyAAAAAAS7vbVtKt5br417G1RSXq89NW3Hff2gOJum5CSqICdJ00Yka1RaDMUGThlFho9QZAAAAAAINZ3dLtW0OFTd0qlDTZ0qq2nT9qoW7ahq0Z6aNjn/Y+hGdlKULsxN14W56SrISZKFY2BxEigyfIQiAwAAAAA+19Hl0uaKnlkbxXvr9WFZvbqcn8/bGBRn06UTM/XtyZkamebrs1sQzCgyfIQiAwAAAACOrc3h1JqdNXqztEpvl1apudPZ99yZQ+L1rUlDdFFehpKiww1MiUBAkeEjFBkAAAAAcGIcTpfe+axa/9p0QKu2V/dtQbGaTZo5Nk0FOYkakhilzIRIZSdFKZFyA19AkXGK7Ha77Ha7XC6XduzYQZEBAAAAAF6obXXo5S0H9a9N+/XpgeZ+X5MaG9F3Gkp+doLOGZnCyo0QRpHhI6zIAAAAAIBTs72yRa99ckhltW060Nih/Q3tqmp2HPU6k0kalxGnc0el6vJJzNgINRQZPkKRAQAAAAC+1+Zwamd1q3ZUtqj0ULPW76nTtsqWI14zZWiirp6ara9PGKzIcItBSXG6UGT4CEUGAAAAAJwe1S2den9XrV77pFLvbKuW6/CMjaTocM2fMVzXTRuq6AirwSkxUCgyfIQiAwAAAABOv6rmTj27oUJPFVfoQGOHJCkxKkw/mDFc887OUVQ4hUawocjwEYoMAAAAADCO0+XWis0Htfidndpb1y5JykyI1J2XjtMFY9MNTgdfosjwEYoMAAAAADCe0+XWS1sO6t43d/St0Pj6hEG6/aJxSo+zGZwOvkCR4SMUGQAAAADgP9q7nFr09k49vLZMLrdHUeEW/fDc4Zo/YzjzMwIcRYaPUGQAAAAAgP8pPdisX6/4RB/ta5QkpcRE6MZZo3T+6FTFR4UpNsIqk8lkbEh4hSLDRygyAAAAAMA/eTwevfrJId3zxnaVH56f0ctskjITI/WtSUN0VUG2BsWz/cTfUWT4CEUGAAAAAPi3LqdbT5fs07L39+pQU6c6ul1HPG8xmzTrjDTNnzFcU3KSDEqJL0OR4SMUGQAAAAAQWDq7XWrq6Nb6PXV6Yv0+Fe+t73vuq7npunnOWA1PjTEwIfpDkeEjFBkAAAAAENh2VLVo6doyPbOhQm6PZDWbdE1htn45e4xibWFGx8NhFBk+QpEBAAAAAMFhR1WL/vTvbXpnW7UkaVhKtOzXTFJuBu/1/IE377/NpykTAAAAAACGGZ0eq6XfK9ATPyhURrxNZbVtuuxv7+up4n3i7/cDC0UGAAAAACBknD0yRa/eMEMzx6TK4XTrluc/0c+Xb1ZzZ7fR0XCCKDIAAAAAACElMTpcD88t0K++NlYWs0kvbj6oOYvW6MM9dUZHwwmgyAAAAAAAhByz2aSfnD9Cz/zoLGUlRepAY4euenC9/vz6NnU53UbHw3FQZAAAAAAAQtbkoUn6943n6orJQ+TxSH9ftVvffWi9alocRkfDMVBkAAAAAABCWkyEVfdckacl/zVJsRFWlext0MWL1+rj/Y1GR0M/KDIAAAAAAJD0tfGDtWLB2RqeGq1DTZ26Ysk6PfrBXtW2sjrDn5g8QX7OTEVFha699lpVV1fLarXq1ltv1RVXXHFCX+vNObYAAAAAgODQ3Nmtnz+9We9sq+67Nj4zTjNGpercUamaPDRR4VbWBfiSN++/g77IOHTokKqqqpSfn6/KykpNnjxZO3bsUHR09Jd+LUUGAAAAAIQmt9ujh9bu0QsfHdRnh5qPeC4q3KKzhifr3FEpmjE6VcNTomUymQxKGhwoMo4jLy9Pr7zyirKysr70tRQZAAAAAIDq5k6t3VWrNTtrtWZnjWpbu454PjMhUueOTtGMUak6e0SK4qPCDEoauLx5/234WpjVq1froosuUkZGhkwmk1asWHHUa+x2u3JycmSz2VRYWKji4uKT+l4bN26Uy+U6oRIDAAAAAABJSouz6fJJQ/SXK/NV/P9m6dUbztHNc8Zq+ohkhVvMOtDYoaeKK/TTJzZp4p1v6rK/va/73tqh9Xvq5HC6jI4fdKxGB2hra1NeXp6uv/56XX755Uc9v3z5ci1cuFBLlixRYWGhFi1apNmzZ2v79u1KS0uTJOXn58vpdB71tW+++aYyMjIkSfX19bruuuv04IMPDuwPBAAAAAAIWmazSeMy4jUuI14/Pm+E2ruc+nBPvVbvrNGanbXaVd2qj/Y16qN9jbp/5U5FWM2akpOoyUOTNCEzXuMz4zQozsZWlFPgV1tLTCaTXnjhBV166aV91woLC1VQUKDFixdLktxut7KysvSzn/1MN9988wnd1+Fw6MILL9T8+fN17bXXHvd1Dsfn02ibm5uVlZXF1hIAAAAAwAk50NihtYdLjfV76o7ahiJJydHhGpcZr/EZcRqfGa+xg2KVlRSlMIvhmyYM483WEsNXZBxPV1eXNm7cqFtuuaXvmtls1qxZs7Ru3boTuofH49H3/n979x4U5XX/cfyzoCyoICLKJSjgXRTRICrReInEy6RWYxyN2gZjamrEaVSkqc0oBqcBbU1SUxI7bUdMJ6MmbU1irEyN15GgESoxGiXiD2OMIpEIgldgz++P1G02IGJqXHZ5v2Z23H3O2bPfg9/ZefhynvPMmqWHHnqowSKGJKWnp+uFF174n2IGAAAAADRf9/n7aFpcZ02L6yxjjIpKq5T7f2U6fKZCR76s0InSKpVdvqG9n32lvZ99ZX+fp4dFYe18FN6+tSLbt1JEYGtFBLZWp3at1MHXKj/vFqzi+I8mXci4cOGCamtrFRQU5HA8KChIx48fb9QYOTk52rRpk/r162fff+Ovf/2roqOj6/RdsmSJFi1aZH99c0UGAAAAAAB3ymKxqHuQr7oH+dqPXauu1fGSSh35skJHz1boky8rdLL0sq5W1+rzsiv6vOyK9tYzlpenhzr4WtU7xFeDIgM0KLK9+oT6NctVHE26kHE3DBs2TDabrVF9rVarrFbrDxwRAAAAAKC58m7pqf6d/NW/k7/9mDFGpZXXVXzhsk5duKzism/+PXXhis6WX1Xl9RrdqLXpy/Kr+rL8qj44Vmp/r8UieVos8vCwyNNikaeHRR6Wb1Z4LBnfW1Pj3O+P8026kBEYGChPT0+dP3/e4fj58+cVHBzspKgAAAAAALh7LBaLgvy8FeTnrSFd2tdpv1ZdqwtV11VScU2HTpfro1Nf6+Cpr1V+pVrGSDXGSLa621/eqG3cH/VdTZMuZHh5eSk2NlY7duywbwBqs9m0Y8cOzZ8//wf73MzMTGVmZqq2ltvkAAAAAACcy7ulp8LatVJYu1YaGBGgOcO7yGYzKr9arRqbTTabVGuMbDajWpuxP+/g655XHDi9kFFVVaWioiL76+LiYhUUFCggIECdO3fWokWLlJiYqIEDB2rQoEF65ZVXdPnyZT355JM/WExJSUlKSkqy75oKAAAAAEBT4uFhUUBrL2eH4RROL2Tk5eVp1KhR9tc3N9tMTExUVlaWpk2bpq+++krLli1TSUmJ+vfvr+zs7DobgAIAAAAAAPdnMcbUvZAGku7sPrYAAAAAAOD7uZPfv5vffVoaITMzU1FRUYqLi3N2KAAAAAAA4FtYkdEAVmQAAAAAAPDDY0UGAAAAAABwSxQyAAAAAACAy6CQAQAAAAAAXAaFjHqw2ScAAAAAAE0Tm302gM0+AQAAAAD44bHZJwAAAAAAcEsUMgAAAAAAgMugkAEAAAAAAFwGhQwAAAAAAOAyKGTUg7uWAAAAAADQNHHXkgZw1xIAAAAAAH543LUEAAAAAAC4JQoZAAAAAADAZVDIAAAAAAAALoNCBgAAAAAAcBkUMgAAAAAAgMugkFEPbr8KAAAAAEDTxO1XG8DtVwEAAAAA+OFx+1UAAAAAAOCWKGQAAAAAAACXQSEDAAAAAAC4DAoZAAAAAADAZVDIAAAAAAAALoNCBgAAAAAAcBkUMgAAAAAAgMugkFGPzMxMRUVFKS4uztmhAAAAAACAb7EYY4yzg2iqKioq5O/vry+++EJ+fn7ODgcAAAAAALd06dIlderUSeXl5Wrbtm2DfVvco5hcUmVlpSSpU6dOTo4EAAAAAAD3V1lZedtCBisyGmCz2XT27Fn5+vrKYrE4O5xGu1nJYiUJXBl5DHdAHsMdkMdwB+Qx3IU757IxRpWVlQoNDZWHR8O7YLAiowEeHh4KCwtzdhjfm5+fn9slN5of8hjugDyGOyCP4Q7IY7gLd83l263EuInNPgEAAAAAgMugkAEAAAAAAFwGhQw3ZLValZqaKqvV6uxQgO+NPIY7II/hDshjuAPyGO6CXP4Gm30CAAAAAACXwYoMAAAAAADgMihkAAAAAAAAl0EhAwAAAAAAuAwKGQAAAAAAwGVQyHAzmZmZioiIkLe3twYPHqyPPvrI2SEBt7R8+XJZLBaHR69evezt165dU1JSktq3b682bdroscce0/nz550YMSDt3btXEyZMUGhoqCwWi9555x2HdmOMli1bppCQEPn4+CghIUEnTpxw6PP1119r5syZ8vPzk7+/v5566ilVVVXdw1kAt8/lWbNm1fmOHjdunEMfchnOlJ6erri4OPn6+qpjx46aNGmSCgsLHfo05lzi9OnTeuSRR9SqVSt17NhRKSkpqqmpuZdTQTPXmFweOXJkne/kuXPnOvRpTrlMIcONbNq0SYsWLVJqaqr+/e9/KyYmRmPHjlVpaamzQwNuqU+fPjp37pz9sW/fPnvbwoULtWXLFr399tvas2ePzp49q8mTJzsxWkC6fPmyYmJilJmZWW/7qlWrtGbNGq1du1YHDhxQ69atNXbsWF27ds3eZ+bMmTp69Ki2b9+u999/X3v37tXTTz99r6YASLp9LkvSuHHjHL6jN2zY4NBOLsOZ9uzZo6SkJO3fv1/bt29XdXW1xowZo8uXL9v73O5cora2Vo888ohu3LihDz/8UOvXr1dWVpaWLVvmjCmhmWpMLkvSnDlzHL6TV61aZW9rdrls4DYGDRpkkpKS7K9ra2tNaGioSU9Pd2JUwK2lpqaamJiYetvKy8tNy5Ytzdtvv20/duzYMSPJ5Obm3qMIgYZJMps3b7a/ttlsJjg42Pz2t7+1HysvLzdWq9Vs2LDBGGPMp59+aiSZgwcP2vts27bNWCwW8+WXX96z2IFv+24uG2NMYmKimThx4i3fQy6jqSktLTWSzJ49e4wxjTuX+Oc//2k8PDxMSUmJvc/rr79u/Pz8zPXr1+/tBID/+G4uG2PMiBEjzLPPPnvL9zS3XGZFhpu4ceOG8vPzlZCQYD/m4eGhhIQE5ebmOjEyoGEnTpxQaGiounTpopkzZ+r06dOSpPz8fFVXVzvkdK9evdS5c2dyGk1WcXGxSkpKHPK2bdu2Gjx4sD1vc3Nz5e/vr4EDB9r7JCQkyMPDQwcOHLjnMQMN2b17tzp27KiePXvqmWeeUVlZmb2NXEZTU1FRIUkKCAiQ1LhzidzcXEVHRysoKMjeZ+zYsbp06ZKOHj16D6MH/uu7uXzTm2++qcDAQPXt21dLlizRlStX7G3NLZdbODsA3B0XLlxQbW2tQ+JKUlBQkI4fP+6kqICGDR48WFlZWerZs6fOnTunF154QQ8++KCOHDmikpISeXl5yd/f3+E9QUFBKikpcU7AwG3czM36votvtpWUlKhjx44O7S1atFBAQAC5jSZl3Lhxmjx5siIjI3Xy5En9+te/1vjx45WbmytPT09yGU2KzWbTggULNHToUPXt21eSGnUuUVJSUu939s024F6rL5clacaMGQoPD1doaKgOHz6s5557ToWFhfrHP/4hqfnlMoUMAE4zfvx4+/N+/fpp8ODBCg8P11tvvSUfHx8nRgYAePzxx+3Po6Oj1a9fP3Xt2lW7d+/W6NGjnRgZUFdSUpKOHDnisNcW4Ipulcvf3n8oOjpaISEhGj16tE6ePKmuXbve6zCdjktL3ERgYKA8PT3r7MJ8/vx5BQcHOykq4M74+/urR48eKioqUnBwsG7cuKHy8nKHPuQ0mrKbudnQd3FwcHCdTZhramr09ddfk9to0rp06aLAwEAVFRVJIpfRdMyfP1/vv/++du3apbCwMPvxxpxLBAcH1/udfbMNuJdulcv1GTx4sCQ5fCc3p1ymkOEmvLy8FBsbqx07dtiP2Ww27dixQ/Hx8U6MDGi8qqoqnTx5UiEhIYqNjVXLli0dcrqwsFCnT58mp9FkRUZGKjg42CFvL126pAMHDtjzNj4+XuXl5crPz7f32blzp2w2m/2kBGiKzpw5o7KyMoWEhEgil+F8xhjNnz9fmzdv1s6dOxUZGenQ3phzifj4eH3yyScORbnt27fLz89PUVFR92YiaPZul8v1KSgokCSH7+RmlcvO3m0Ud8/GjRuN1Wo1WVlZ5tNPPzVPP/208ff3d9i5FmhKkpOTze7du01xcbHJyckxCQkJJjAw0JSWlhpjjJk7d67p3Lmz2blzp8nLyzPx8fEmPj7eyVGjuausrDSHDh0yhw4dMpLMSy+9ZA4dOmQ+//xzY4wxGRkZxt/f37z77rvm8OHDZuLEiSYyMtJcvXrVPsa4cePMgAEDzIEDB8y+fftM9+7dzfTp0501JTRTDeVyZWWlWbx4scnNzTXFxcXmgw8+MPfff7/p3r27uXbtmn0MchnO9Mwzz5i2bdua3bt3m3PnztkfV65csfe53blETU2N6du3rxkzZowpKCgw2dnZpkOHDmbJkiXOmBKaqdvlclFRkUlLSzN5eXmmuLjYvPvuu6ZLly5m+PDh9jGaWy5TyHAzr776quncubPx8vIygwYNMvv373d2SMAtTZs2zYSEhBgvLy9z3333mWnTppmioiJ7+9WrV828efNMu3btTKtWrcyjjz5qzp0758SIAWN27dplJNV5JCYmGmO+uQXr0qVLTVBQkLFarWb06NGmsLDQYYyysjIzffp006ZNG+Pn52eefPJJU1lZ6YTZoDlrKJevXLlixowZYzp06GBatmxpwsPDzZw5c+r8cYRchjPVl7+SzLp16+x9GnMucerUKTN+/Hjj4+NjAgMDTXJysqmurr7Hs0FzdrtcPn36tBk+fLgJCAgwVqvVdOvWzaSkpJiKigqHcZpTLluMMeberf8AAAAAAAD4/tgjAwAAAAAAuAwKGQAAAAAAwGVQyAAAAAAAAC6DQgYAAAAAAHAZFDIAAAAAAIDLoJABAAAAAABcBoUMAAAAAADgMihkAACAH8ypU6dksVhUUFDg7FDsjh8/riFDhsjb21v9+/d3dji3tHv3blksFpWXlzs7FAAAmhQKGQAAuLFZs2bJYrEoIyPD4fg777wji8XipKicKzU1Va1bt1ZhYaF27Njh7HAAAMAdopABAICb8/b21sqVK3Xx4kVnh3LX3Lhx43u/9+TJkxo2bJjCw8PVvn37uxgVAAC4FyhkAADg5hISEhQcHKz09PRb9lm+fHmdyyxeeeUVRURE2F/PmjVLkyZN0osvvqigoCD5+/srLS1NNTU1SklJUUBAgMLCwrRu3bo64x8/flwPPPCAvL291bdvX+3Zs8eh/ciRIxo/frzatGmjoKAg/fSnP9WFCxfs7SNHjtT8+fO1YMECBQYGauzYsfXOw2azKS0tTWFhYbJarerfv7+ys7Pt7RaLRfn5+UpLS5PFYtHy5ctvOU56eroiIyPl4+OjmJgY/e1vf7O337zsY+vWrerXr5+8vb01ZMgQHTlyxGGcv//97+rTp4+sVqsiIiK0evVqh/br16/rueeeU6dOnWS1WtWtWzf95S9/ceiTn5+vgQMHqlWrVnrggQdUWFhob/v44481atQo+fr6ys/PT7GxscrLy6t3TgAAuAsKGQAAuDlPT0+9+OKLevXVV3XmzJn/aaydO3fq7Nmz2rt3r1566SWlpqbqRz/6kdq1a6cDBw5o7ty5+vnPf17nc1JSUpScnKxDhw4pPj5eEyZMUFlZmSSpvLxcDz30kAYMGKC8vDxlZ2fr/Pnzmjp1qsMY69evl5eXl3JycrR27dp64/v973+v1atX63e/+50OHz6ssWPH6sc//rFOnDghSTp37pz69Omj5ORknTt3TosXL653nPT0dL3xxhtau3atjh49qoULF+onP/lJnQJMSkqKVq9erYMHD6pDhw6aMGGCqqurJX1TgJg6daoef/xxffLJJ1q+fLmWLl2qrKws+/ufeOIJbdiwQWvWrNGxY8f0xz/+UW3atHH4jOeff16rV69WXl6eWrRoodmzZ9vbZs6cqbCwMB08eFD5+fn61a9+pZYtW97qvw8AAPdgAACA20pMTDQTJ040xhgzZMgQM3v2bGOMMZs3bzbfPg1ITU01MTExDu99+eWXTXh4uMNY4eHhpra21n6sZ8+e5sEHH7S/rqmpMa1btzYbNmwwxhhTXFxsJJmMjAx7n+rqahMWFmZWrlxpjDFmxYoVZsyYMQ6f/cUXXxhJprCw0BhjzIgRI8yAAQNuO9/Q0FDzm9/8xuFYXFycmTdvnv11TEyMSU1NveUY165dM61atTIffvihw/GnnnrKTJ8+3RhjzK5du4wks3HjRnt7WVmZ8fHxMZs2bTLGGDNjxgzz8MMPO4yRkpJioqKijDHGFBYWGklm+/bt9cZx8zM++OAD+7GtW7caSebq1avGGGN8fX1NVlbWLecCAIA7YkUGAADNxMqVK7V+/XodO3bse4/Rp08feXj89/QhKChI0dHR9teenp5q3769SktLHd4XHx9vf96iRQsNHDjQHsfHH3+sXbt2qU2bNvZHr169JH2zn8VNsbGxDcZ26dIlnT17VkOHDnU4PnTo0Duac1FRka5cuaKHH37YIaY33njDIZ7vzisgIEA9e/a0f9axY8fqjeXEiROqra1VQUGBPD09NWLEiAbj6devn/15SEiIJNl/vosWLdLPfvYzJSQkKCMjo058AAC4oxbODgAAANwbw4cP19ixY7VkyRLNmjXLoc3Dw0PGGIdjNy+R+LbvXrZgsVjqPWaz2RodV1VVlSZMmKCVK1fWabv5i7sktW7dutFj/i+qqqokSVu3btV9993n0Ga1Wu/a5/j4+DSq37d/vjfvNHPz57t8+XLNmDFDW7du1bZt25SamqqNGzfq0UcfvWtxAgDQ1LAiAwCAZiQjI0NbtmxRbm6uw/EOHTqopKTEoZhRUFBw1z53//799uc1NTXKz89X7969JUn333+/jh49qoiICHXr1s3hcSfFCz8/P4WGhionJ8fheE5OjqKioho9TlRUlKxWq06fPl0nnk6dOt1yXhcvXtRnn31mn1fv3r3rjaVHjx7y9PRUdHS0bDZbnX037lSPHj20cOFC/etf/9LkyZPr3WwVAAB3wooMAACakejoaM2cOVNr1qxxOD5y5Eh99dVXWrVqlaZMmaLs7Gxt27ZNfn5+d+VzMzMz1b17d/Xu3Vsvv/yyLl68aN+0MikpSX/60580ffp0/fKXv1RAQICKioq0ceNG/fnPf5anp2ejPyclJUWpqanq2rWr+vfvr3Xr1qmgoEBvvvlmo8fw9fXV4sWLtXDhQtlsNg0bNkwVFRXKycmRn5+fEhMT7X3T0tLUvn17BQUF6fnnn1dgYKAmTZokSUpOTlZcXJxWrFihadOmKTc3V3/4wx/02muvSZIiIiKUmJio2bNna82aNYqJidHnn3+u0tLSOhud1ufq1atKSUnRlClTFBkZqTNnzujgwYN67LHHGj1XAABcESsyAABoZtLS0upc+tG7d2+99tpryszMVExMjD766KNb3tHj+8jIyFBGRoZiYmK0b98+vffeewoMDJQk+yqK2tpajRkzRtHR0VqwYIH8/f0d9uNojF/84hdatGiRkpOTFR0drezsbL333nvq3r37HY2zYsUKLV26VOnp6erdu7fGjRunrVu3KjIyss68nn32WcXGxqqkpERbtmyRl5eXpG9Wmrz11lvauHGj+vbtq2XLliktLc3hsp7XX39dU6ZM0bx589SrVy/NmTNHly9fblSMnp6eKisr0xNPPKEePXpo6tSpGj9+vF544YU7misAAK7GYr57QSwAAAAatHv3bo0aNUoXL16Uv7+/s8MBAKBZYUUGAAAAAABwGRQyAAAAAACAy+DSEgAAAAAA4DJYkQEAAAAAAFwGhQwAAAAAAOAyKGQAAAAAAACXQSEDAAAAAAC4DAoZAAAAAADAZVDIAAAAAAAALoNCBgAAAAAAcBkUMgAAAAAAgMugkAEAAAAAAFzG/wOTgTYH9il8zgAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot([v[0] for v in loss_evolution])\n", "plt.yscale(\"log\")\n", "plt.xlabel(\"Number of epochs\")\n", "plt.ylabel(\"Loss function value\")" ] }, { "cell_type": "markdown", "id": "e53fa645", "metadata": {}, "source": [ "## References\n", "\n", "> [1] O. Kyriienko, A. E. Paine, and V. E. Elfving, “Solving nonlinear differential equations with differentiable quantum circuits”, [Physical Review A](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.052416) **103**, 052416 (2021).\n", "\n", "> [2] A. Pérez-Salinas, A. Cervera-Lierta, E. Gil-Fuster, and J. I. Latorre, “Data re-uploading for a universal quantum classifier”, [Quantum](https://quantum-journal.org/papers/q-2020-02-06-226/) **4**, 226 (2020).\n", "\n", "> [3] M. Schuld, R. Sweke, and J. J. Meyer, “Effect of data encoding on the expressive power of variational quantum-machine-learning models”, [Physical Review A]( https://journals.aps.org/pra/abstract/10.1103/PhysRevA.103.032430) **103**, 032430 (2021).\n", "\n", "> [4] B. Y. Gan, D. Leykam, D. G. Angelakis, and D. G. Angelakis, “Fock State-enhanced Expressivity of Quantum Machine Learning Models”, in [Conference on Lasers andElectro-Optics](https://opg.optica.org/abstract.cfm?uri=CLEO_AT-2021-JW1A.73) (2021), paper JW1A.73. Optica Publishing Group, (2021).\n", "\n", "> [5] R. Fletcher, Practical methods of optimization. [John Wiley & Sons](https://onlinelibrary.wiley.com/doi/book/10.1002/9781118723203). (2013)." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Encoding_Tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "581eff927fa04130", "metadata": {}, "source": [ "# Logical encoding" ] }, { "cell_type": "code", "execution_count": 1, "id": "b4e6b6a0317abf4e", "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "from perceval import PS, BS, PERM\n", "import numpy as np" ] }, { "cell_type": "markdown", "id": "441089dff1913c71", "metadata": {}, "source": [ "## I. Path encoding\n", "\n", "To perform quantum computations using photons, we need an encoding: a mapping between our Fock states and our qubit states.\n", "\n", "We therefore want to associate each qubit state with one of our Fock states.\n", "\n", "One natural way to encode qubits is the path encoding.\n", "A qubit is a two-level quantum state, so we will use two spatial modes to encode it: this is the dual-rail or path encoding.\n", "\n", "The logical qubit state $|0\\rangle_L$ will correspond to a photon in the upper mode, as in the Fock state $|1,0\\rangle$, while $|1\\rangle_L$ will be encoded as $|0,1\\rangle$.\n", "\n", "\n", "We can extend this to multiple qubits by having twice as many modes as there are qubits. For example the $3$-qubit state $\\frac{1}{\\sqrt{2}}(|000\\rangle_L+|111\\rangle_L)$ can be encoded with $3$ photons and $3\\times 2=6$ modes :\n", "$\\frac{1}{\\sqrt{2}}(|1,0,1,0,1,0\\rangle+|0,1,0,1,0,1\\rangle)$" ] }, { "cell_type": "markdown", "id": "70fc4196780b7f4d", "metadata": {}, "source": [ "## II. Single-qubit gates\n", "\n", "Using the dual-rail encoding, single-qubit gates only deal with one photon and are straightforward. Can you give the LO-circuits for the gates below?" ] }, { "cell_type": "code", "execution_count": 2, "id": "initial_id", "metadata": {}, "outputs": [], "source": [ "## Exercise: find the LO-circuits for each gate\n", "\n", "## Solution:\n", "circuit_x = PERM([1, 0]) # it's not the only way\n", "circuit_y = PERM([1, 0]) // (0, PS(-np.pi/2)) // (1, PS(np.pi/2))\n", "circuit_z = pcvl.Circuit(2) // (1, PS(np.pi))\n", "circuit_h = BS.H()\n", "\n", "circuit_rx = pcvl.Circuit(2) // (0, PS(np.pi)) // BS.Rx(theta=pcvl.P(\"theta\")) // (0, PS(np.pi))\n", "circuit_ry = BS.Ry(theta=pcvl.P(\"theta\"))\n", "circuit_rz = BS.H() // circuit_rx // BS.H() # Indeed, Rz = H Rx H" ] }, { "cell_type": "markdown", "id": "38efca9db4c8d224", "metadata": {}, "source": [ "## III. Two-qubit gates\n", "\n", "On the other hand, in dual-rail encoding, it can be shown that two-qubit gates can't be deterministic, and have a probability to fail.\n", "\n", "There are two ways to detect that failure:\n", "\n", "- We can use additional photons called ancillas, which we can measure independently from the main circuit photons. Depending on the state obtained on the ancilla, we know whether the gate has succeeded or not on the main qubits. Those gates will be called heralded.\n", "- We can also directly measure the main circuit qubits, and depending on the result, assess whether the gate has succeeded or not. Those gates will be called postselected.\n", "\n", "The CNOT gate acts on two qubits, a control and a target, and flips the value of the target if the control qubit is in state $|1\\rangle_L$. In the following two exercises, we will see the two types of CNOT gates:\n", "- the postselected CNOT of [Ralph et al.](https://arxiv.org/abs/quant-ph/0112088)\n", "- the heralded CNOT of [Knill](https://arxiv.org/abs/quant-ph/0110144)" ] }, { "cell_type": "code", "execution_count": 3, "id": "900a4cb468b7a07d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['klm cnot', 'heralded cnot', 'postprocessed cnot', 'heralded cz', 'postprocessed cz', 'generic 2 mode circuit', 'mzi phase first', 'mzi phase last', 'symmetric mzi', 'postprocessed ccz', 'toffoli', 'postprocessed controlled gate', 'x', 'y', 'z', 'h', 'rx', 'ry', 'rz', 'ph', 's', 'sdag', 't', 'tdag', 'qloq ansatz']\n" ] } ], "source": [ "## We introduce the component catalog. It contains both CNOT gates.\n", "from perceval.components import catalog\n", "print(catalog.list())" ] }, { "cell_type": "code", "execution_count": 4, "id": "d9b59fcc69d0ad14", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "POSTPROCESSED CNOT DOCUMENTATION\n", "---------------------------------\n", "\n", "CNOT gate with 2 heralded modes and a post-selection function\n", "\n", "Scientific article reference: https://journals.aps.org/pra/abstract/10.1103/PhysRevA.65.062324\n", "\n", "Schema:\n", " ╭─────╮\n", "ctrl (dual rail) ─────┤ ├───── ctrl (dual rail)\n", " ─────┤ ├─────\n", " │ │\n", "data (dual rail) ─────┤ ├───── data (dual rail)\n", " ─────┤ ├─────\n", " ╰─────╯\n", "\n", "See also: klm cnot and heralded cnot (using cz)\n", "\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "\n", "\n", "1\n", "\n", "\n", "1\n", "\n", "\n", "0\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## Ralph's et al. CNot\n", "\n", "print(catalog['postprocessed cnot'].doc)\n", "ralph_cnot = catalog['postprocessed cnot'].build_processor()\n", "ralph_cnot.min_detected_photons_filter(0)\n", "## You can set its input state with a LogicalState\n", "ralph_cnot.with_input(pcvl.LogicalState([1, 0]))\n", "\n", "pcvl.pdisplay(ralph_cnot, recursive=True, render_size=1.25)" ] }, { "cell_type": "code", "execution_count": 5, "id": "3502ec9021afea96", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0>, |1,0,1,0> ]\n", "Some output states were not selected because of heralds and post-processing => you can check the global performance\n", "Global performance = 0.0740740740740741\n" ] } ], "source": [ "## Knill CNOT\n", "\n", "h_cnot = catalog['heralded cnot'].build_processor()\n", "cnot_sampler = pcvl.algorithm.Sampler(h_cnot)\n", "h_cnot.with_input(pcvl.LogicalState([0, 0]))\n", "\n", "samples = cnot_sampler.samples(10)\n", "print(samples['results'])\n", "print(\"Some output states were not selected because of heralds and post-processing => you can check the global performance\")\n", "print(\"Global performance = \", samples['global_perf'])" ] }, { "cell_type": "code", "execution_count": 6, "id": "801339e5700112f4", "metadata": {}, "outputs": [], "source": [ "## Exercise: Check/convince yourself that the circuit above is performing a CNOT in the dual rail encoding" ] }, { "cell_type": "code", "execution_count": 7, "id": "f425d106390da81", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", "\t|0,1,0,1>: 1\n", "}\n", "Some output states were not selected because of heralds and post-processing => you can check the global performance\n", "Global performance = 0.11111111111111113\n" ] } ], "source": [ "## You can sample some output states\n", "cnot_sampler = pcvl.algorithm.Sampler(ralph_cnot)\n", "samples = cnot_sampler.probs()\n", "print(samples['results'])\n", "print(\"Some output states were not selected because of heralds and post-processing => you can check the global performance\")\n", "print(\"Global performance = \", samples['global_perf'])" ] }, { "cell_type": "code", "execution_count": 8, "id": "9ffe2e0e8182e11a", "metadata": {}, "outputs": [], "source": [ "## Exercise: Check it performs a CNOT, and explicit the difference between the two types of CNOT" ] }, { "cell_type": "markdown", "id": "1d92110cb9068fa8", "metadata": {}, "source": [ "### Exercise\n", "\n", "The next circuit comes from the following [paper](https://quantum-journal.org/papers/q-2021-03-29-422/)." ] }, { "cell_type": "markdown", "id": "d874a8784f827c01", "metadata": {}, "source": [ "![](../_static/img/tuto_circuit_to_reproduce.png)" ] }, { "cell_type": "code", "execution_count": 9, "id": "8f30d5b5acc26532", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "RY1\n", "\n", "\n", "\n", "RZ4\n", "\n", "\n", "\n", "RY2\n", "\n", "\n", "\n", "RZ5\n", "\n", "\n", "\n", "RY3\n", "\n", "\n", "\n", "RZ6\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "HERALDED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "HERALDED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "HERALDED CNOT\n", "\n", "\n", "\n", "\n", "\n", "RY7\n", "\n", "\n", "\n", "RZ10\n", "\n", "\n", "\n", "RY8\n", "\n", "\n", "\n", "RZ11\n", "\n", "\n", "\n", "RY9\n", "\n", "\n", "\n", "RZ12\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald2]\n", "1\n", "\n", "[herald3]\n", "1\n", "\n", "[data]\n", "\n", "[herald6]\n", "1\n", "\n", "[herald7]\n", "1\n", "\n", "[herald10]\n", "1\n", "\n", "[herald11]\n", "1\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "1\n", "\n", "[herald1]\n", "1\n", "\n", "[data]\n", "\n", "[herald4]\n", "1\n", "\n", "[herald5]\n", "1\n", "\n", "[herald8]\n", "1\n", "\n", "[herald9]\n", "1\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "## Exercise: reproduce it in the encoding seen above\n", "\n", "## Solution:\n", "# Let's try to implement that circuit properly.\n", "# First, the quantum gates, as coded above :\n", "\n", "Rx = lambda i: pcvl.Circuit(2) // (0, PS(np.pi)) // BS.Rx(theta=pcvl.P(f\"theta{i}\")) // (0, PS(np.pi)) #Be careful with the minus ! We use a convention\n", "Ry = lambda i: pcvl.Circuit(2,name=f\"Ry{i}\") // BS.Ry(theta=pcvl.P(f\"theta{i}\"))\n", "Rz = lambda i: pcvl.Circuit(2,name=f\"Rz{i}\") // BS.H() // circuit_rx // BS.H()\n", "cnot = catalog['heralded cnot'].build_processor()\n", "\n", "# Our qubits in the dual rail encoding\n", "q1, q2, q3 = [0,1], [2,3], [4,5]\n", "\n", "p = pcvl.Processor(\"SLOS\",6)\n", "\n", "for i in range(3):\n", " p.add(2*i,Ry(i+1)).add(2*i,Rz(i+4))\n", "p.add(q1+q2, cnot)\n", "p.add(q1+q3, cnot)\n", "p.add(q2+q3, cnot)\n", "\n", "for i in range(3):\n", " p.add(2*i, Ry(i+7)).add(2*i, Rz(i+10))\n", "\n", "pcvl.pdisplay(p, recursive=False)" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Gedik_qudit.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "357ebaaf", "metadata": {}, "source": [ "# Gedik's algorithm with qudit encoding" ] }, { "cell_type": "markdown", "id": "4a6f1669", "metadata": {}, "source": [ "In this notebook we implement Gedik's Algorithm (a quantum algorithm that provides computational speedup and solves a black-box problem), inspired by the following papers:\n", "\n", "[1] Gedik, Zafer, et al. “Computational speed-up with a single qudit.” Scientific reports 5.1 (2015): 14671.\n", "\n", "[2] Zhan, Xiang, et al. “Linear optical demonstration of quantum speed-up with a single qudit.” Optics Express 23.14 (2015): 18422-18427. " ] }, { "cell_type": "markdown", "id": "3a21ee99fb46bf5b", "metadata": { "collapsed": false }, "source": [ "## Introduction" ] }, { "cell_type": "markdown", "id": "51f897b6", "metadata": {}, "source": [ "This notebook is about implementing Gedik's Algorithm using Perceval. The specific algorithm is considered a Quantum Algorithm which tries to solve a problem by taking advantage of the attributes of a Quantum Computer. Gedik's algorithm is used to solve a black-box problem. The main task of the algorithm is to determine whether a specific permutation is clockwise or counterclockwise. This translates as a problem of finding the parity of the permutation. In order to implement the code efficiently, we used one qudit with 4 dimensions (ququart). \n", "\n", "Gedik's algorithm provides an example of a quantum algorithm that provides a speed-up compared to its classical implementation. This happens because in the classical case at least two function evaluations are required to find the parity of the permutation. For the quantum case only one function evaluation is enough to determine the parity of the permutation." ] }, { "cell_type": "code", "execution_count": 1, "id": "fd9fbb7a", "metadata": {}, "outputs": [], "source": [ "from time import sleep\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import perceval as pcvl\n", "import perceval.components as comp\n", "from perceval.algorithm import Sampler" ] }, { "cell_type": "markdown", "id": "40ed29c2", "metadata": {}, "source": [ "The algorithm can be broken down in three parts. The Quantum Fourier Transform (QFT), the permutation operation and the inverse Quantum Fourier Transform." ] }, { "cell_type": "markdown", "id": "b551c9f4", "metadata": {}, "source": [ "## Permutation Operation" ] }, { "cell_type": "markdown", "id": "709d4e3a", "metadata": {}, "source": [ "This part contains all the code that is necessary to implement all the different permutation types. We have eight different permutation operators that can be implemented in various ways using Perceval. The gates that we need to implement are the CNOT and the PAULI X." ] }, { "cell_type": "code", "execution_count": 2, "id": "d4be6a95", "metadata": {}, "outputs": [], "source": [ "# Because we used qudit encoding the CNOT gates can be implemented as unitaries. \n", "# We can also use Perceval's PERM function for the implementation.\n", "# The following is the unitary of a CNOT gate.\n", "cnot_m_01 = pcvl.Matrix([[1,0,0,0],\n", " [0,1,0,0],\n", " [0,0,0,1],\n", " [0,0,1,0]])\n", "\n", "# This is a CNOT gate with different control and target qubits.\n", "cnot_m_10 = pcvl.Matrix([[1, 0, 0, 0],\n", " [0, 0, 0, 1],\n", " [0, 0, 1, 0],\n", " [0, 1, 0, 0]])\n", "\n", "cnot_01 = comp.Unitary(U = cnot_m_01)\n", "cnot_10 = comp.Unitary(U = cnot_m_10)" ] }, { "cell_type": "code", "execution_count": 3, "id": "63056235", "metadata": {}, "outputs": [], "source": [ "# We can do the same with the X gates. We can implement them based on unitaries \n", "# Because of the qudit encoding XI or IX or XX must be implemented.\n", "# We can also use PERM for the implementation as well.\n", "X = np.array([[0,1],\n", " [1,0]])\n", "\n", "XI_m = np.kron(X, np.eye(2))\n", "XI_mp = pcvl.Matrix(XI_m)\n", "\n", "IX_m = np.kron(np.eye(2), X)\n", "IX_mp = pcvl.Matrix(IX_m)\n", "\n", "XX_m = np.kron(X, X)\n", "XX_mp = pcvl.Matrix(XX_m)\n", "\n", "XI = comp.Unitary(U = XI_mp)\n", "IX = comp.Unitary(U = IX_mp)\n", "XX = comp.Unitary(U = XX_mp)" ] }, { "cell_type": "code", "execution_count": 4, "id": "43ab128d", "metadata": {}, "outputs": [], "source": [ "def perm(number, cnot, process):\n", " \"\"\"\n", " Function that creates all the permutations in a Perceval's process encoding.\n", " Inputs:\n", " number (int): the number of the permutation.\n", " cnot (unitary): the type of CNOT to be used in the experiment.\n", " process (process): the process on which the permutations will be added.\n", " Outputs:\n", " process (process): the process that will contain all the permutations\n", " \"\"\"\n", " \n", " if number == 1:\n", " process.add(0, cnot)\n", " process.add(0, IX)\n", " \n", " elif number == 2:\n", " process.add(0, XI)\n", " \n", " elif number == 3:\n", " process.add(0, cnot)\n", " process.add(0, XX)\n", " \n", " elif number == 4:\n", " process.add(0, cnot)\n", " \n", " elif number == 5:\n", " process.add(0, IX)\n", " \n", " elif number == 6:\n", " process.add(0, cnot)\n", " process.add(0, XI)\n", " \n", " elif number == 7:\n", " process.add(0, XX)\n", " \n", " print(f'Permutation {number}: Completed')\n", " \n", " return process" ] }, { "cell_type": "markdown", "id": "5a7d88b5", "metadata": {}, "source": [ "## Quantum Fourier Transform (QFT)" ] }, { "cell_type": "markdown", "id": "90937564", "metadata": {}, "source": [ "There are a lot of different ways to implement the QFT in Perceval. One of them is to decompose the QFT circuit to one-qubit gates and CNOTs in order to make it easier to apply. However, using the qudit encoding we can also implement the QFT using its unitary matrix." ] }, { "cell_type": "code", "execution_count": 5, "id": "66a3599e", "metadata": {}, "outputs": [], "source": [ "# Implementation of QFT by using its unitary matrix.\n", "# This works because we use qudit encoding.\n", "qft = pcvl.Matrix([[1/2, 1/2, 1/2, 1/2],\n", " [1/2, 1j/2, -1/2, -1j/2],\n", " [1/2, -1/2, 1/2, -1/2],\n", " [1/2, -1j/2, -1/2, 1j/2]])\n", "\n", "qft_unit = comp.Unitary(U = qft)" ] }, { "cell_type": "markdown", "id": "8099dde0", "metadata": {}, "source": [ "## Inverse Quantum Fourier Transform (Inverse QFT)" ] }, { "cell_type": "markdown", "id": "aec976d9", "metadata": {}, "source": [ "The same holds for the Inverse QFT. It is not easy to be implemented in Perceval. However, using the qudit encoding it becomes much easier to make it work." ] }, { "cell_type": "code", "execution_count": 6, "id": "7c82f552", "metadata": {}, "outputs": [], "source": [ "# Implementation of inverse QFT by using its unitary matrix.\n", "inv_qft = qft.inv()\n", "\n", "inv_qft_unit = comp.Unitary(U = inv_qft)" ] }, { "cell_type": "markdown", "id": "2346197d", "metadata": {}, "source": [ "## Implementation of the process using Perceval" ] }, { "cell_type": "markdown", "id": "0b5d399c", "metadata": {}, "source": [ "Creation of a process in Perceval in order to implement the Quantum Circuit needed for Gedik's Algorithm. All the necessary gates will be added to the specific process." ] }, { "cell_type": "markdown", "id": "9bc8f374", "metadata": {}, "source": [ "We have to define the input state that will be used in the process. \n", "With the qudit encoding the input state will be different from the regular qubit encoding.\n", "The mapping will be as follows.\n", "\n", "$|00\\rangle$: $|1,0,0,0\\rangle$ \n", "\n", "$|01\\rangle$: $|0,1,0,0\\rangle$ \n", "\n", "$|10\\rangle$: $|0,0,1,0\\rangle$ \n", "\n", "$|11\\rangle$: $|0,0,0,1\\rangle$ \n", "\n", "For Gedik's Algorithm the input state must be the $|01\\rangle$ state. " ] }, { "cell_type": "code", "execution_count": 7, "id": "d73e4fb0", "metadata": {}, "outputs": [], "source": [ "def algo_proc(perm_no, qft_ver, inv_qft_ver):\n", " \"\"\"\n", " Function that implements Gedik's algorithm in a non-noisy simulator.\n", " Gives us the theoretical results. Useful because we will compare them with experimental.\n", " Inputs: \n", " perm_no (int): the number of the permutation that we want to test.\n", " qft (unitary): the type of the QFT to be used.\n", " inv_qft (unitary): the type of the inverse QFT to be used.\n", " Outputs: \n", " res (dict): the result of the sim process with probs.\n", " \"\"\" \n", " assert perm_no in range(8), 'perm_no can take values from 0 to 7.'\n", " \n", " pr = pcvl.Processor(\"SLOS\", 4)\n", " \n", " # Add the QFT procedure in the process.\n", " pr.add(0, qft_ver)\n", " \n", " # Add the permutation operator that has to be checked, by choosing a number between 0 and 7.\n", " # The first four correspond to plus permutations and (theoretically) we expect them to return |01> \n", " # while the other four correspond to minus permutations and (theoretically) we expect them to return |11>.\n", " perm(perm_no, cnot_10, pr)\n", " \n", " # The final part of Gedik's algorithm is to insert the inverse QFT procedure.\n", " pr.add(0, inv_qft_ver)\n", " \n", " input_state = pcvl.BasicState([0,1,0,0])\n", " \n", " pr.with_input(input_state)\n", " \n", " res = pr.probs()['results']\n", " \n", " return res" ] }, { "cell_type": "code", "execution_count": 8, "id": "c0d84243", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Permutation 0: Completed\n", "{\n", " |0,1,0,0>: 1.0\n", "}\n", "Permutation 1: Completed\n", "{\n", " |0,1,0,0>: 1.0\n", "}\n", "Permutation 2: Completed\n", "{\n", " |0,1,0,0>: 1.0\n", "}\n", "Permutation 3: Completed\n", "{\n", " |0,1,0,0>: 1.0\n", "}\n", "Permutation 4: Completed\n", "{\n", " |0,0,0,1>: 1.0\n", "}\n", "Permutation 5: Completed\n", "{\n", " |0,0,0,1>: 1.0\n", "}\n", "Permutation 6: Completed\n", "{\n", " |0,0,0,1>: 1.0\n", "}\n", "Permutation 7: Completed\n", "{\n", " |0,0,0,1>: 1.0\n", "}\n" ] } ], "source": [ "# Run the circuit using as the initial state the one that was defined above.\n", "for i in range(8):\n", " print(algo_proc(i, qft_unit, inv_qft_unit))" ] }, { "cell_type": "markdown", "id": "2c1fb45e", "metadata": {}, "source": [ "## Try the implementation of the algorithm in a noisy simulator" ] }, { "cell_type": "markdown", "id": "a5fc6d5f", "metadata": {}, "source": [ "We will use Quandela's remote processor in order to let Gedik's algorithm run in a noisy simulator. \n", "This will help us have a better look regarding the efficiency of the implementation. In this section we will use a simulator of the QPU that is provided via \"sim:ascella\"." ] }, { "cell_type": "code", "execution_count": null, "id": "fd28f794", "metadata": {}, "outputs": [], "source": [ "# Save your token and proxies into Perceval persistent data, you only need to do it once.\n", "pcvl.RemoteConfig.set_token(\"MY_TOKEN\")\n", "pcvl.RemoteConfig.set_proxies({\"https\": \"socks5h://USER:PASSWORD@HOST:PORT\"})\n", "pcvl.RemoteConfig().save()\n", "\n", "remote_pr = pcvl.RemoteProcessor(\"sim:ascella\")" ] }, { "cell_type": "code", "execution_count": 22, "id": "45a99207", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Permutation 3: Completed\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Define the physical parameters that we need.\n", "remote_pr.noise = pcvl.NoiseModel(indistinguishability=.95, transmittance=.1, g2=.01)\n", "remote_pr.min_detected_photons_filter(1)\n", "\n", "# Add all the necessary components to the processor.\n", "# We can try different permutations by modifying the number variable of the perm function.\n", "# The numbers that are valid are from 0 to 7.\n", "remote_pr.add(0, qft_unit)\n", "perm(3, cnot_10, remote_pr)\n", "remote_pr.add(0, inv_qft_unit)" ] }, { "cell_type": "code", "execution_count": 23, "id": "4f8fc28c", "metadata": {}, "outputs": [], "source": [ "# Define and run the processor with the predefined input state\n", "input_state = pcvl.BasicState([0,1,0,0])\n", "\n", "remote_pr.with_input(input_state)" ] }, { "cell_type": "code", "execution_count": 24, "id": "653a2620", "metadata": {}, "outputs": [], "source": [ "# We can use the sampler module in order to get the results of the algorithm.\n", "sampler = Sampler(remote_pr, max_shots_per_call=10000000)\n", "sampler.default_job_name = \"Gediks_Algo\"\n", "\n", "nsample = 100000\n", "remote_job = sampler.sample_count.execute_async(nsample)" ] }, { "cell_type": "code", "execution_count": 26, "id": "933c5974", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " |0,1,0,0>: 100000\n", "}\n" ] } ], "source": [ "# Get the results once they are available after the completion of sampling.\n", "while not remote_job.status.completed:\n", " sleep(1)\n", "results = remote_job.get_results()\n", "print(results['results'])" ] }, { "cell_type": "markdown", "id": "92cdcb25", "metadata": {}, "source": [ "We will use Quandela's remote processor in order to let Gedik's algorithm run in a noisy simulator. This will help us have a better look regarding the efficiency of the implementation. In this section we will use the actual QPU that is provided via \"qpu:ascella\"." ] }, { "cell_type": "code", "execution_count": 28, "id": "c2f41bad", "metadata": {}, "outputs": [], "source": [ "remote_qpu = pcvl.RemoteProcessor(\"qpu:ascella\")\n", "\n", "def algo_qpu(perm_no, qft_ver, inv_qft_ver):\n", " \"\"\"\n", " Function that implements Gedik's algorithm with a format that will be run on the QPU.\n", " Inputs: \n", " perm_no (int): the number of the permutation to be added.\n", " qft (unitary): the unitary that will implement the QFT procedure.\n", " inv_qft (unitary): the unitary that will implement the inverse QFT procedure.\n", " token (str): the token to implement the experiment in the QPU.\n", " Outputs:\n", " remote_job_qpu: creates a remote job object that will be used to run the \n", " experiments on the QPU\n", " \"\"\"\n", " remote_qpu.clear_input_and_circuit()\n", " remote_qpu.add(0, qft_ver)\n", " perm(perm_no, cnot_10, remote_qpu)\n", " remote_qpu.add(0, inv_qft_ver)\n", " remote_qpu.min_detected_photons_filter(1)\n", " \n", " input_state = pcvl.BasicState([0,1,0,0])\n", "\n", " remote_qpu.with_input(input_state)\n", " \n", " sampler_on_qpu = Sampler(remote_qpu, max_shots_per_call=10000000)\n", "\n", " nsample = 200000\n", " remote_job_qpu = sampler_on_qpu.sample_count \n", " remote_job_qpu.name = \"Gediks_Algo_QPU\"\n", " remote_job_qpu.execute_async(nsample)\n", " \n", " return remote_job_qpu" ] }, { "cell_type": "code", "execution_count": 39, "id": "ad6655b8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Permutation 0: Completed\n", "Permutation 1: Completed\n", "Permutation 2: Completed\n", "Permutation 3: Completed\n", "Permutation 4: Completed\n", "Permutation 5: Completed\n", "Permutation 6: Completed\n", "Permutation 7: Completed\n" ] } ], "source": [ "# Run the algorithm on the QPU for all the permutations\n", "jobs = []\n", "for i in range(8):\n", " jobs.append(algo_qpu(i, qft_unit, inv_qft_unit))" ] }, { "cell_type": "code", "execution_count": 41, "id": "9df02c29", "metadata": {}, "outputs": [], "source": [ "# Get the results once they are available\n", "data_qpu = []\n", "for i in range(8):\n", " while not jobs[i].status.completed:\n", " sleep(1)\n", " data_qpu.append(jobs[i].get_results()['results'])" ] }, { "cell_type": "code", "execution_count": null, "id": "54bf8cc3", "metadata": {}, "outputs": [], "source": [ "# Transform the results returned from Ascella to percentages to add them to \n", "# plots and compare them with the non-noisy results.\n", "data_qpu_prob = []\n", "for res_qpu in data_qpu:\n", " total = sum(res_qpu.values())\n", " res_qpu_dict = {k: v / total for k, v in res_qpu.items()}\n", " data_qpu_prob.append(res_qpu_dict)" ] }, { "cell_type": "markdown", "id": "684c8e13", "metadata": {}, "source": [ "## Create Plots" ] }, { "cell_type": "markdown", "id": "697a7aae", "metadata": {}, "source": [ "Modify the data from the results in order to plot it properly." ] }, { "cell_type": "code", "execution_count": null, "id": "53c67163", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Permutation 0: Completed\n", "Permutation 1: Completed\n", "Permutation 2: Completed\n", "Permutation 3: Completed\n", "Permutation 4: Completed\n", "Permutation 5: Completed\n", "Permutation 6: Completed\n", "Permutation 7: Completed\n" ] } ], "source": [ "# Create a state map that maps the results that from Perceval using the qudit encoding \n", "# with the states in the qubit encoding.\n", "state_map = {\n", " '|1,0,0,0>': '00',\n", " '|0,1,0,0>': '01',\n", " '|0,0,1,0>': '10',\n", " '|0,0,0,1>': '11'\n", " }\n", "\n", "# Run the algorithm with all the permutations and store the results.\n", "data_n = []\n", "for i in range(8):\n", " data_n.append(algo_proc(i, qft_unit, inv_qft_unit))" ] }, { "cell_type": "code", "execution_count": null, "id": "2a01f130", "metadata": {}, "outputs": [], "source": [ "def cr_data(res_map, st_map):\n", " \"\"\"\n", " Function that is used to map the states with qudit encoding to the ones with qubit encoding\n", " and also add the probabilities of each state after the completion of the algorithm.\n", " Inputs: \n", " res_map (dict): dictionary that contains all the data from the experiments.\n", " state_map (dict): a dictionary that contains a mapping between Fock space states (in qudit encoding)\n", " and Hilbert space states in qubit encoding. \n", " Outputs:\n", " dict_f (dict): a dictionary containing all possible states in qubit encoding and their respective\n", " probabilities of occurrence.\n", " \"\"\"\n", " dict_f = {'00': 0,\n", " '01': 0,\n", " '10': 0,\n", " '11': 0}\n", "\n", " for k,v in res_map.items():\n", " for state in st_map.keys():\n", " if str(k) == state:\n", " dict_f[state_map[str(k)]] += v\n", "\n", " return dict_f" ] }, { "cell_type": "code", "execution_count": null, "id": "2aec5807", "metadata": {}, "outputs": [], "source": [ "# Use of cr_data function in order to modify the data for all the permutations in order to make it ready for plotting.\n", "data_plt = []\n", "for i in range(8):\n", " data_plt.append(cr_data(data_n[i], state_map))\n", " \n", "data_plt_qpu = []\n", "for i in range(8):\n", " data_plt_qpu.append(cr_data(data_qpu_prob[i], state_map))" ] }, { "cell_type": "code", "execution_count": null, "id": "c5cf2fc1", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 100, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAysAAAHeCAYAAACbl5TfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABncElEQVR4nO3de1yUdf7//+eAHFUQRUEMBS07fCoPmIilaZFom2W6G2mluWpbHjJZKzUVrVzMTm5F+a310EGTctMOulqRWhZlqZi7HlLTcDUQNEFRQeH6/eHPWScODsNwzYHH/Xab2415z3XN+zXM0wtfcx3GYhiGIQAAAABwMz6uLgAAAAAAKkOzAgAAAMAt0awAAAAAcEs0KwAAAADcEs0KAAAAALdEswIAAADALdGsAAAAAHBLNCsAAAAA3FIDVxcAAACA2isrK9OZM2dcXQZQLT8/P/n6+tq9PM0KAACABzMMQ7m5uTp27JirSwHs0qRJE0VGRspisVx0WZoVAAAAD3a+UWnRooWCg4Pt+g8g4AqGYejkyZM6fPiwJKlly5YXXYdmBQAAwEOVlZVZG5VmzZq5uhzgooKCgiRJhw8fVosWLS56SBgn2AMAAHio8+eoBAcHu7gSwH7n82rPOVY0KwAAAB6OQ7/gSWqSV5oVAAAAAG6JZgUAAABuyWKxaMWKFS6tISYmRnPnzq12mRkzZqhjx46m1GOWRYsWqUmTJq4ugxPsAQAAvFHMpJWmzrd/9h9qtHx+fr6mT5+ulStXKi8vT2FhYerQoYOmT5+u66+/XpL066+/KiwsrC7Ktdv333+vhg0bWu9bLBYtX75cAwYMML2WGTNmaMWKFcrOzq7zuZKTk3XrrbfW+TwXQ7MCAAAA0w0aNEilpaV688031bZtW+Xl5SkzM1NHjhyxLhMZGenCCs9p3ry5q0twiaCgIOuVu1yJw8CcYNGiRbJYLNZbYGCg2rdvr7FjxyovL8/V5dnl2LFjeuCBB9S8eXM1bNhQvXv31ubNm11dFirh6Xn79ddfNWnSJPXu3VuNGzeWxWLRunXrXF0WquDpecvMzNSf//xntW/fXsHBwWrbtq1GjhypX3/91dWloQqenrkvv/xSt99+u6KjoxUYGKjIyEj17dtXX3/9tatLcyvHjh3TV199pWeeeUa9e/dWmzZt1LVrV02ePFm33367dbkLDwPbv3+/LBaL3nvvPfXo0UNBQUG67rrr9NNPP+n7779Xly5d1KhRI/Xr10/5+flVzt2lSxc999xz1vsDBgyQn5+fTpw4IUn673//K4vFoj179kiyPQwsJiZGknTnnXfKYrFY75/39ttvKyYmRqGhobr77rt1/Phx62MlJSV6+OGH1aJFCwUGBuqGG27Q999/b328ssOuVqxYYT0ZfdGiRZo5c6a2bt1q/fexaNGiSl/j/fffrwEDBui5555Ty5Yt1axZM40ZM8bm6lu//fabhg4dqrCwMAUHB6tfv37avXt3lfVs3brV+rc7JCREcXFx+uGHH6yPb9iwwfq+REdH6+GHH1ZxcXGl9dUEzYoTPfnkk3r77bf1yiuvqHv37nrttdeUkJCgkydPurq0apWXl+sPf/iDlixZorFjx2rOnDk6fPiwevXqZRNauBdPzduuXbv0zDPP6ODBg7rmmmtcXQ7s5Kl5e/zxx7Vu3Trdeeedeumll3T33XfrvffeU6dOnZSbm+vq8lANT83cTz/9JB8fHz344INKT0/XxIkTlZubq549e2r16tWuLs9tNGrUSI0aNdKKFStUUlJSo3VTU1M1depUbd68WQ0aNNCQIUP02GOP6e9//7u++uor7dmzR9OnT69y/RtvvNH6IZlhGPrqq6/UpEkTbdiwQZK0fv16tWrVSpdeemmFdc83FwsXLtSvv/5q02zs3btXK1as0CeffKJPPvlE69ev1+zZs62PP/bYY/rnP/+pN998U5s3b9all16qpKQkHT161K7XnZycrL/+9a/6v//7P/3666/69ddflZycXOXya9eu1d69e7V27Vq9+eabWrRokU1zc//99+uHH37QRx99pKysLBmGoVtvvbXKywnfc889uuSSS/T9999r06ZNmjRpkvz8/KyvvW/fvho0aJB+/PFHZWRkaMOGDRo7dqxdr61aBmpt4cKFhiTj+++/txlPSUkxJBlLliyp9RzFxcW1fo6qZGRkGJKM999/3zp2+PBho0mTJsbgwYOrXff8a4d5PD1vRUVFxpEjRwzDMIz333/fkGSsXbvWrnXXrl1rSDL27dtXZ/XBlqfnbf369UZZWVmFMUnGE088Ue265M01PD1zVc0XERFhJCUlVbucI5k7deqUsX37duPUqVMVHmvz+Cem3mpq2bJlRlhYmBEYGGh0797dmDx5srF161abZSQZy5cvNwzDMPbt22dIMv7xj39YH3/33XcNSUZmZqZ1LC0tzbj88surnPejjz4yQkNDjbNnzxrZ2dlGZGSkMX78eOPxxx83DMMwRo4caQwZMuR/v8c2bYwXX3yx0prOS01NNYKDg42ioiLr2KOPPmrEx8cbhmEYJ06cMPz8/IzFixdbHy8tLTWioqKMOXPmGIZxLvuhoaE2z7t8+XKb/2elpqYaHTp0qPK1nTds2DCjTZs2xtmzZ61jf/rTn4zk5GTDMAzjp59+MiQZX3/9tfXxgoICIygoyHjvvfcqradx48bGokWLKp1vxIgRxgMPPGAz9tVXXxk+Pj6VZrO63P4ee1bq0E033SRJ2rdvn3XsnXfeUVxcnIKCgtS0aVPdfffdOnDggM16vXr10tVXX61NmzapZ8+eCg4O1pQpU6y7P5977jmlp6erbdu2Cg4OVp8+fXTgwAEZhqGnnnpKl1xyiYKCgnTHHXfY1a0vW7ZMERERGjhwoHWsefPmuuuuu/Thhx/W+BMPuIan5K1x48Zq2rSpc188TOcpeevZs6d8fHwqjDVt2lQ7duxwwm8CZvGUzFUmODhYzZs317Fjxxx+/d5o0KBBOnTokD766CP17dtX69atU+fOnas8tOm8a6+91vpzRESEJNnsqY+IiNDhw4erXL9Hjx46fvy4tmzZovXr1+vGG29Ur169rHtb1q9fr169etX49cTExKhx48bW+y1btrTWsXfvXp05c8Z64QBJ8vPzU9euXetsW/R///d/Nt8Of2E9O3bsUIMGDRQfH299vFmzZrr88surrCclJUUjR45UYmKiZs+erb1791of27p1qxYtWmTdY9aoUSMlJSWpvLzc5t+sIzjBvg6dfxObNWsmSZo1a5amTZumu+66SyNHjlR+fr5efvll9ezZU1u2bLE5LvDIkSPq16+f7r77bt17773Wf4yStHjxYpWWlmrcuHE6evSo5syZo7vuuks33XST1q1bp8cff1x79uzRyy+/rIkTJ2rBggXV1rllyxZ17ty5wh/0rl276vXXX9dPP/3E4ToewFPyBu/gyXk7ceKETpw4ofDw8Nr9EmAqT8tcUVGRSktLVVBQoLfeekv//ve/NWXKFOf9QrxEYGCgbrnlFt1yyy2aNm2aRo4cqdTUVN1///1VrnP+0CPpf18u+Pux8vLyKtdv0qSJOnTooHXr1ikrK0u33HKLevbsqeTkZP3000/avXu3brzxxhq/lgtrsKeO3/Px8ZFhGDZj9nzDe13V83szZszQkCFDtHLlSv3rX/9Samqqli5dqjvvvFMnTpzQX/7yFz388MMV1mvdurXDc0o0K05VWFiogoICnT59Wl9//bWefPJJBQUF6bbbbtMvv/yi1NRUPf300zYbq4EDB6pTp0569dVXbcZzc3M1b948/eUvf7GO7d+/X5J08OBB7d69W6GhoZKksrIypaWl6dSpU/rhhx/UoMG5tzU/P1+LFy/Wa6+9poCAgCrr/vXXX9WzZ88K4y1btpQkHTp0iGbFDXlq3uCZvClvc+fOVWlpabXHesP1PD1zd911l9asWSNJ8vf311/+8hdNmzat1r8Xb3fVVVeZ8r0qN954o9auXauNGzdq1qxZatq0qa688krNmjVLLVu2VPv27atc18/PT2VlZTWar127dvL399fXX3+tNm3aSDrXiHz//fd65JFHJJ07quX48eMqLi62Xir595co9vf3r/Hclbnyyit19uxZfffdd+revbukc039rl27dNVVV1W5Xvv27dW+fXtNmDBBgwcP1sKFC3XnnXeqc+fO2r59e6Xn+dQWh4E5UWJiopo3b67o6GjdfffdatSokZYvX65WrVrpgw8+UHl5ue666y4VFBRYb5GRkbrsssu0du1am+cKCAjQ8OHDK53nT3/6k3WjKsm6C+/ee++1blTPj5eWlurgwYPV1n3q1KlKN7yBgYHWx8/77bffbOo/f+WMC8cKCgrc/gRIb+CpeauJ8/9ZOX8rLCyUVHUOUXe8JW9ffvmlZs6caf3k/ELkzb14euZmz56tTz/9VPPnz1e3bt1UWlqqs2fP2ixTnzN35MgR3XTTTXrnnXf0448/at++fXr//fc1Z84c3XHHHXU+f69evbRmzRo1aNBAV1xxhXVs8eLFF92rEhMTo8zMTOXm5uq3336za76GDRvqoYce0qOPPqrVq1dr+/btGjVqlE6ePKkRI0ZIOpex84cp7t27V0uWLKlwSFxMTIz27dun7OxsFRQUOHyo/mWXXaY77rhDo0aN0oYNG7R161bde++9atWqVaW//1OnTmns2LFat26dfvnlF3399df6/vvvdeWVV0o6dzGTb775RmPHjlV2drZ2796tDz/80Ckn2LNnxYnS09PVvn17NWjQQBEREbr88suth1bt3r1bhmHosssuq3Td3++qa9Wqlfz9/Std9ve7085vZKOjoysdv9g/pKCgoErDfvr0aevj53Xq1Em//PJLhWV/fw3y1NRUzZgxo9p5UTuemreauOOOO7R+/foK4507d7a5P2zYsIse44za8Ya87dy5U3feeaeuvvpq/eMf/6jwOHlzL56euQu/zfzee+9V586ddf/992vZsmXW8brOXE2/pNFMjRo1Unx8vF588UXr+RzR0dEaNWqUKYfL9ejRQ+Xl5TaNSa9evfT3v//9ouerPP/880pJSdEbb7yhVq1aWffSXczs2bNVXl6u++67T8ePH1eXLl20Zs0a65deNm3aVO+8844effRRvfHGG7r55ps1Y8YMPfDAA9bnGDRokD744AP17t1bx44d08KFC6s9ZK46Cxcu1Pjx43XbbbeptLRUPXv21KpVqyr8+5EkX19fHTlyREOHDlVeXp7Cw8M1cOBAzZw5U9K584jWr1+vJ554Qj169JBhGGrXrp1T9mDTrDhR165d1aVLl0ofKy8vl8Vi0b/+9S+bk53Oa9Sokc396r6Ep7L1qxv//fGPv9eyZctKv3Pg/FhUVJR1bPHixTZ7Wj799FM9++yz+uyzz2zWbdu2bbVzovY8NW818fzzz9v8x2Dr1q2aOHGi3nnnHZtjzi/MKOqGp+ftwIED6tOnj0JDQ7Vq1Sqbk2DPI2/uxdMzdyF/f3/dfvvtmj17tk6dOmWtpz5nLiAgQGlpaUpLS6t2uQt/3zExMRV+/7169aowdv/991/0P/BNmzatcP7GgAEDKn1/f9+M9O/fX/3797cZmzFjRoUPaR955BHrIV7SuSNWXnrpJb300ktV1jVgwAANGDDAZmzUqFHWnwMCAmwa3qpU1tye/66Y88LCwvTWW29V+RwX/h79/f317rvvVjvnddddp08//fSitdUUzYpJ2rVrJ8MwFBsbW+1xkK7QsWNHffXVVyovL7c5yf67775TcHCwTb0XXsVCOvfFSdK53fVwH+6ct5qIi4uzuX/+kIzrr7++whdxwXXcPW9HjhxRnz59VFJSoszMTOv5eL9H3jyHu2euMqdOnZJhGDp+/Li1WSFzwMVxzopJBg4cKF9fX82cObNC124Yho4cOeKiyqQ//vGPysvL0wcffGAdKygo0Pvvv6/+/ftzsrQHcue8wfu4c96Ki4t166236uDBg1q1alWVhw3Bs7hz5iq7ZO6xY8f0z3/+U9HR0WrRooULqgI8F3tWTNKuXTs9/fTTmjx5svbv368BAwaocePG2rdvn5YvX64HHnhAEydOdEltf/zjH9WtWzcNHz5c27dvV3h4uF599VWVlZVZj0WEZ3HnvEnS008/LUn6z3/+I0l6++23rd8cPHXqVJfVBce4c97uuecebdy4UX/+85+1Y8cOm+8PaNSoUYXDLeAZ3Dlz/fr10yWXXKL4+Hi1aNFCOTk5WrhwoQ4dOqSMjAyX1AR4MpoVE02aNEnt27fXiy++aG0CoqOj1adPH91+++0uq8vX11erVq3So48+qpdeekmnTp3Sddddp0WLFunyyy93WV2oHXfNm6QKl++88HsLaFY8k7vm7fxlPxcsWFDh+zHatGlDs+LB3DVzf/7zn7V06VK9+OKLOnbsmMLCwtStWzctWbJEPXr0cFldgKeyGM48GxYAAACmOX36tPbt26fY2FjrVw4A7q4mueWcFQAAAABuiWYFAAAAgFuiWQEAAADglmhWAAAAALglmhUAAADUSxaLRStWrJB07pvqLRaL9SqCtTFt2jQ98MADtX6e2li9erU6duyo8vJyl9ZRW1y6GAAAwBvNCDV5vkKHVsvKytINN9ygvn37auXKlU4uyny5ubn6+9//rm3bttmMHzhwQKmpqVq9erUKCgrUsmVLDRgwQNOnT1ezZs2sy/Xq1Uvr16+XJAUEBKht27YaO3asRo8eLUmaMWOGVqxYUaGp2r9/v2JjY7VlyxZ17NhRffv21bRp07R48WLdd999dfui61C9a1bKy8t16NAhNW7cWBaLxdXlwE6GYej48eOKioqSj49n7RAkc56HvMFM5A21UVpaqvLycpWVlamsrMzmMV8X1VRT8+fP17hx4zR//nwdOnRIUVFRri6pVv7xj3+oe/fuatOmjXXs559/VkJCgtq3b693331XsbGx+s9//qNHH31U//rXv/Ttt9+qadOm1uVHjRqlJ598UidPntRbb72lMWPGKCwsTIMHD65RLffff79eeuklmhVPcujQIUVHR7u6DDjowIEDuuSSS1xdRo2QOc9F3mAm8gZHtGnTRvPmzdOpU6cqPNbFBfXU1IkTJ5SRkaEffvhBubm5WrRokaZMmWJ9/LffftPYsWP16aef6sSJE7rkkks0ZcoUDR8+XJL03//+V48++qjWrFmjkpISXXnllUpPT1d8fLwk6cMPP9TMmTO1fft2RUVFadiwYXriiSfUoMHF/wtcVlamBx54QF988YVyc3PVunVrjR49WuPHj692vaVLl+qhhx6yGRszZoz8/f316aefKigoSJLUunVrderUSe3atdMTTzyh1157zbp8cHCwIiMjJZ3bk7JkyRJ99NFHNW5W+vfvr7Fjx2rv3r1q165djdZ1F/WuWWncuLGkc38UQkJCXFwN7FVUVKTo6Gjr++dJyJznIW8wE3lDbZSWliovL08xMTEVv1zvE9fUVBPvvfeerrjiCl1++eW699579cgjj2jy5MnWPXXTpk3T9u3b9a9//Uvh4eHas2ePtTE7ceKEbrzxRrVq1UofffSRIiMjtXnzZus5Gl999ZWGDh2ql156ST169NDevXut55GkpqZetLby8nJdcsklev/999WsWTN98803euCBB9SyZUvdddddla5z9OhRbd++XV26dLEZW7NmjWbNmmVtVM6LjIzUPffco4yMDL366qtV7qEMCgpSaWnpRWv+vdatWysiIkJfffUVzYqnOB+CkJAQNqweyBMPMyBznou8wUzkDY44ffq08vPz5evrK19fTznw63/mz5+ve++9V5LUt29fFRYWav369erVq5ckKScnR506dbL+5z8mJsa67pIlS5Sfn6/vv//eegjVpZdean185syZmjRpkoYNGyZJatu2rZ566ik99thjdjUrfn5+mjlzpvV+bGyssrKy9N5771XZrOTk5MgwDJtD2Xbv3i3DMHTllVdWus6VV16p3377Tfn5+WrRooXNY2VlZXr33Xf1448/OnzCflRUlH755ReH1nUH9a5ZAQAAgOvt2rVLGzdu1PLlyyVJDRo0UHJysubPn29tVh566CENGjRImzdvVp8+fTRgwAB1795dkpSdna1OnTrZnOtxoa1bt+rrr7/WrFmzrGNlZWU6ffq0Tp48qeDg4IvWmJ6ergULFignJ0enTp1SaWmpOnbsWOXy5/f6VNjLpXPnp1XH39/f+vOrr76qf/zjHyotLZWvr68mTJhQ4dAyewUFBenkyZMOresOXHom35dffqn+/fsrKirK5tJx1Vm3bp06d+6sgIAAXXrppVq0aFGd1wnvQN5gNjIHM5E3eJr58+fr7NmzioqKUoMGDdSgQQO99tpr+uc//6nCwnNXFuvXr59++eUXTZgwQYcOHdLNN9+siRMnSlKFQ6p+78SJE5o5c6ays7Ott23btmn37t2VNhO/t3TpUk2cOFEjRozQp59+quzsbA0fPrzaw7HCw8MlnTvX5rxLL71UFotFO3bsqHSdHTt2qHnz5mrSpIl17J577lF2drb27dun4uJivfDCC9YLcISEhFh/Pxc6duyYJCk01PYqcEePHlXz5s0v+nrdlUubleLiYnXo0EHp6el2Lb9v3z794Q9/UO/evZWdna1HHnlEI0eO1Jo1a+q4UngD8gazkTmYibzBk5w9e1ZvvfWWnn/+eZtmYuvWrYqKitK7775rXbZ58+YaNmyY3nnnHc2dO1evv/66JOnaa69Vdna2jh49WukcnTt31q5du3TppZdWuNlz5b2vv/5a3bt31+jRo9WpUyddeuml2rt3b7XrtGvXTiEhIdq+fbt1rFmzZrrlllv06quvVrgQQm5urhYvXqz777/fZjw0NFSXXnqpWrVqVaHWyy+/XP/973+Vl5dnM75582YFBgaqdevW1rHTp09r79696tSp00Vfr7ty6WFg/fr1U79+/exeft68eYqNjdXzzz8v6dwxfhs2bNCLL76opKSkuioTXoK8wWxkDmYib/Akn3zyiX777TeNGDGiwp6AQYMGaf78+XrwwQc1ffp0xcXF6f/+7/9UUlKiTz75xHrux+DBg/W3v/1NAwYMUFpamlq2bKktW7YoKipKCQkJmj59um677Ta1bt1af/zjH+Xj46OtW7fq3//+t55++umL1njZZZfprbfe0po1axQbG6u3335b33//vWJjY6tcx8fHR4mJidqwYYMGDBhgHX/llVfUvXt3JSUl6emnn7a5dHH79u01ffp0u393SUlJuvzyyzV48GA9/fTT1gsLTJ06VePHj7c5d+nbb79VQECAEhIS7H5+d+NR56xkZWUpMTHRZiwpKUmPPPJIleuUlJSopKTEer+oqKiuyoOXcSRvEpmD49jGwUzkrR6o4ksay8rKtGXLFnXq1MllJ+XPnz9fiYmJFRoV6VyzMmfOHP3444/y9/fX5MmTtX//fgUFBalHjx5aunSpJFkvBfzXv/5Vt956q86ePaurrrrKuncxKSlJn3zyiZ588kk988wz8vPz0xVXXKGRI0faVeNf/vIX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fAxJxuzZs23G+/btawQFBRlFRUU1/p2gbnl65n4vISHBaN68uU3jA6BqHAZWh9LT09W+fXs1aNBAERERuvzyy62HGOzevVuGYVR5mMvvL2fYqlUr+fv7V7ps69atbe6HhoZKkqKjoysd/+2336qte/z48erevbsGDRpU7XL5+fk25xc0atRIjRo1qnYd1B1PzVtlbrjhBsXHx+vzzz+3jpE39+KpeQsKClJxcXGFE7IHDx6s1atXa8uWLerZsyd5c0OemrkL/fzzz8rKytLYsWPVoIHtf8HIHFA5mpU61LVr1yqvblReXi6LxaJ//etf8vX1rfD47zdQQUFBVc5T2frVjRvVnEz6xRdfaPXq1frggw+0f/9+6/jZs2d16tQp7d+/X02bNlVISIiuu+46/fLLL9ZlUlNTNWPGjCqfG3XLE/NWnejoaO3atct6n7y5F0/NW1RUlHbv3m1zjoIktWjRQtL//uNJ3tyPp2buQkuWLJFU+VXAyBxQOZoVF2nXrp0Mw1BsbKzat2/v6nKscnJyJEkDBw6s8NjBgwcVGxurF198UY888ogWL16sU6dOWR9v27ataXWiZtw1b9X5+eef1bx5c+t98uY53DlvcXFx2r17tw4ePGiToUOHDkmSNXPkzbO4c+YutGTJErVr107dunWr8BiZAyrHZU9cZODAgfL19dXMmTMrfCpjGIaOHDnikrpuuukmLV++vMKtefPm6tKli5YvX67+/ftLkq6//nolJiZab2xY3Ze75k06d+jD761atUqbNm1S3759rWPkzXO4c96Sk5MlSfPnz7eOlZeXa+HChWratKni4uIkkTdP486ZO2/Lli3asWOHhgwZUunjZA6oHHtWXKRdu3Z6+umnNXnyZO3fv18DBgxQ48aNtW/fPi1fvlwPPPCAJk6caHpdrVu3rnC8riQ98sgjioiI0IABA0yvCbXnrnmTpO7du6tTp07q0qWLQkNDtXnzZi1YsEDR0dGaMmWKS2pC7bhz3u644w7dfPPNSktLU0FBgTp06KAVK1Zow4YN+n//7/8pICDAJXWhdtw5c+ctXrxYEl8ECdQUzYoLTZo0Se3bt9eLL76omTNnSjp3nH6fPn10++23u7g6eBt3zVtycrJWrlypTz/9VCdPnlTLli01atQopaamVjivAJ7DXfNmsVi0YsUKTZ06VRkZGVq0aJEu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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Create subplots for the results of all the permutations after applying Gedik's algorithm.\n", "# The results are identical to those from the paper.\n", "width = 0.4\n", "r = np.arange(len(data_plt[0].keys()))\n", "\n", "fig, axs = plt.subplots(2, 4)\n", "for i in range(8):\n", " a = i // 4\n", " b = i - 4 * a\n", " l1 = axs[a, b].bar(r - 0.2, data_plt[i].values(), width=width)\n", " l2 = axs[a, b].bar(r + 0.2, data_plt_qpu[i].values(), width=width)\n", " axs[a, b].set_title(f'Perm {i}+')\n", "\n", "fig.tight_layout()\n", "\n", "plt.setp(axs, xticks=r, xticklabels=data_plt[0].keys())\n", "\n", "axs[0,0].set(ylabel='Probability')\n", "axs[1,0].set(ylabel='Probability')\n", "\n", "fig.legend(('Sim without noise', 'Ascella (QPU)'), bbox_to_anchor=(1.24, 1))" ] }, { "cell_type": "markdown", "id": "36264c0262d96153", "metadata": { "collapsed": false }, "source": [ "## References" ] }, { "cell_type": "markdown", "id": "5521e245bcb7700e", "metadata": { "collapsed": false }, "source": [ "[1] Gedik, Zafer, et al. \"Computational speed-up with a single qudit.\" Scientific reports 5.1 (2015): 14671.\n", "\n", "[2] Zhan, Xiang, et al. \"Linear optical demonstration of quantum speed-up with a single qudit.\" Optics Express 23.14 (2015): 18422-18427." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Graph_States_Tutorial.ipynb ================================================ { "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "cc7d159c", "metadata": {}, "source": [ "# Graph States" ] }, { "attachments": {}, "cell_type": "markdown", "id": "a404cf6a", "metadata": {}, "source": [ "## I. Some definitions and properties of graph states" ] }, { "attachments": {}, "cell_type": "markdown", "id": "81150d0f", "metadata": {}, "source": [ "Graph states are specific entangled states that are represented by a graph. They have interesting properties in many fields of quantum computing [[1]](#References), therefore they are points of interest." ] }, { "attachments": {}, "cell_type": "markdown", "id": "ba744e0b", "metadata": {}, "source": [ "### Definition" ] }, { "attachments": {}, "cell_type": "markdown", "id": "3673c194", "metadata": {}, "source": [ "Two definitions of a graph state exist. Since they are equivalent, we will only consider the following:\n", "\n", "Given a graph $G=(V,E)$, with the set of vertices $V$ and the set of edges $E$, the corresponding graph state is defined as:\n", "

\n", "$\\left|G\\right\\rangle = \\prod_{(a,b)\\in E} CZ^{\\{a,b\\}} \\left|+\\right\\rangle^{\\otimes V}$\n", "

\n", "\n", "where $|+\\rangle = \\frac{|0\\rangle + |1\\rangle}{\\sqrt2}$ and $CZ^{\\{a,b\\}}$ is the controlled-Z interaction between the two vertices (corresponding to two qubits) $a$ and $b$. The operators order in the product doesn't matter since CZ gates commute between themselves. We can write the CZ gate with the following matrix :\n", "

\n", "$CZ = \\begin{bmatrix}\n", "1 & 0 & 0 & 0 \\\\\n", "0 & 1 & 0 & 0 \\\\\n", "0 & 0 & 1 & 0 \\\\\n", "0 & 0 & 0 & -1 \\\\\n", "\\end{bmatrix}$\n", "

\n", "\n", "
\n", "\n", "Therefore, we can write the action of a CZ gate as: $CZ^{\\{1,2\\}}|0\\rangle_1 |\\pm\\rangle_2 = |0\\rangle_1 |\\pm\\rangle_2$ and $CZ^{\\{1,2\\}}|1\\rangle_1 |\\pm\\rangle_2 = |1\\rangle_1 |\\mp\\rangle_2$.\n", "\n", "Let’s illustrate this with an example. The associated graph of: $|\\Psi_{graph}\\rangle = CZ^{\\{0,1\\}}\\, CZ^{\\{1,2\\}}\\, CZ^{\\{0,2\\}}\\, CZ^{\\{3,2\\}}|+\\rangle_0 |+\\rangle_1 |+\\rangle_2 |+\\rangle_3$ is the following. The graph is generated as an output later in this notebook. We will now see several ways to create and display these states." ] }, { "attachments": {}, "cell_type": "markdown", "id": "ebe8e04c", "metadata": {}, "source": [ "## II. Generating entangled states with a circuit" ] }, { "attachments": {}, "cell_type": "markdown", "id": "91ceb745", "metadata": {}, "source": [ "We will first use a 3-qubits circuit. We need to implement two CZ gates on it to generate the 3-qubits linear graph state.\n", "\n", "These gates are post-selected CZ gates which have a probability of success of $\\frac{1}{9}$." ] }, { "cell_type": "code", "execution_count": 1, "id": "7a1cbb98", "metadata": {}, "outputs": [], "source": [ "import math\n", "import perceval as pcvl\n", "import networkx as nx\n", "from perceval.utils import StateGenerator, Encoding" ] }, { "attachments": {}, "cell_type": "markdown", "id": "922551ad", "metadata": {}, "source": [ "### Implementation in Perceval" ] }, { "attachments": {}, "cell_type": "markdown", "id": "f21b829c", "metadata": {}, "source": [ "For this circuit, we use path encoded qubits with 3 photons as input." ] }, { "cell_type": "code", "execution_count": 2, "id": "db028030", "metadata": {}, "outputs": [], "source": [ "# Modes number of the circuit\n", "m = 10" ] }, { "cell_type": "code", "execution_count": 3, "id": "f2465169", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "POSTPROCESSED CZ\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSED CZ\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n", "" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Creation of the full circuit\n", "cz = pcvl.catalog[\"postprocessed cz\"].build_circuit()\n", "\n", "c_graph_lin = pcvl.Circuit(10, name=\"C_Graph\")\\\n", " .add((1, 2), pcvl.BS()).add(1, pcvl.PS(math.pi/2))\\\n", " .add((3, 4), pcvl.BS()).add(3, pcvl.PS(math.pi/2))\\\n", " .add((7, 8), pcvl.BS()).add(7, pcvl.PS(math.pi/2))\\\n", " .add(0, cz, merge=False)\\\n", " .add((3,4,5,6), pcvl.PERM([2, 3, 0, 1]))\\\n", " .add(4, cz, merge=False)\\\n", " .add(8, pcvl.PS(math.pi/2)).add(7, pcvl.PS(math.pi/2))\\\n", " .add((3,4,5,6), pcvl.PERM([2, 3, 0, 1]))\n", "\n", "pcvl.pdisplay(c_graph_lin, recursive=True, render_size=0.6)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "e5ea1345", "metadata": {}, "source": [ "#### Logical states" ] }, { "attachments": {}, "cell_type": "markdown", "id": "4c9781d0", "metadata": {}, "source": [ "Logical states are path encoded on the Fock States.\n", "\n", "Due to post-selection, only few states of the full Fock space are relevant.\n", "\n", "- Mode 0,5,6,9 are auxiliary.\n", "- 1st qubit is path encoded in modes 1 & 2\n", "- 2nd qubit in 3 & 4\n", "- 3rd qubit in 7 & 8" ] }, { "cell_type": "code", "execution_count": 4, "id": "68a7ae69", "metadata": {}, "outputs": [], "source": [ "# Basis for three qubits\n", "states = [\n", " pcvl.BasicState([0,1,0,1,0,0,0,1,0,0]), #|000>\n", " pcvl.BasicState([0,1,0,1,0,0,0,0,1,0]), #|001>\n", " pcvl.BasicState([0,1,0,0,1,0,0,1,0,0]), #|010>\n", " pcvl.BasicState([0,1,0,0,1,0,0,0,1,0]), #|011>\n", " pcvl.BasicState([0,0,1,1,0,0,0,1,0,0]), #|100>\n", " pcvl.BasicState([0,0,1,1,0,0,0,0,1,0]), #|101>\n", " pcvl.BasicState([0,0,1,0,1,0,0,1,0,0]), #|110>\n", " pcvl.BasicState([0,0,1,0,1,0,0,0,1,0]) #|111>\n", "]" ] }, { "attachments": {}, "cell_type": "markdown", "id": "10dc4d3e", "metadata": {}, "source": [ "### Computation of the output state" ] }, { "attachments": {}, "cell_type": "markdown", "id": "06fba24d", "metadata": {}, "source": [ "We will then simulate this circuit using the `SLOS` backend and compute the amplitudes for the output state." ] }, { "cell_type": "code", "execution_count": 5, "id": "96ffbb6e", "metadata": {}, "outputs": [], "source": [ "# Simulator\n", "backend = pcvl.BackendFactory.get_backend(\"SLOS\")\n", "backend.set_circuit(c_graph_lin)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "edbb6115", "metadata": {}, "source": [ "We use the state $|000\\rangle$ as input state." ] }, { "cell_type": "code", "execution_count": 6, "id": "21e83796", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The output state is : 0.118*|0,0,1,0,1,0,0,1,0,0>-0.118*|0,1,0,1,0,0,0,1,0,0>-0.02*|0,1,0,0,1,0,0,0,1,0>+0.118*|0,1,0,1,0,0,0,0,1,0>+0.687*|0,0,1,1,0,0,0,0,1,0>+0.02*|0,1,0,0,1,0,0,1,0,0>-0.687*|0,0,1,1,0,0,0,1,0,0>-0.118*|0,0,1,0,1,0,0,0,1,0>\n" ] } ], "source": [ "# Input state\n", "input_state = pcvl.BasicState([0, 1, 0, 1, 0, 0, 0, 1, 0, 0])\n", "backend.set_input_state(input_state)\n", "\n", "# Output state\n", "output_state = pcvl.StateVector()\n", "for state in states:\n", " ampli = backend.prob_amplitude(state)\n", " output_state += ampli*pcvl.StateVector(state)\n", "\n", "print(\"The output state is :\", output_state)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "4861c742", "metadata": {}, "source": [ "As wanted, we obtain the linear graph states for three qubits which is : $\\frac{1}{\\sqrt 8} (|000\\rangle + |001\\rangle + |010\\rangle - |011\\rangle + |100\\rangle + |101\\rangle - |110\\rangle + |111\\rangle )$.\n", "\n", "This state is also locally equivalent to a $GHZ$ state and we can therefore obtain it by performing local unitary single qubit transformations. To visualize this state using `plot_state_qsphere` from *Qiskit* [[2]](#References) or `plot_schmidt` from *qutip* [[3]](#References), follow the `StatevectorConverter` example from *perceval-interop* [[4]](#References)." ] }, { "attachments": {}, "cell_type": "markdown", "id": "d79d56af", "metadata": {}, "source": [ "## III. Generate a state from a graph" ] }, { "attachments": {}, "cell_type": "markdown", "id": "880a6f36", "metadata": {}, "source": [ "We also developed a tool which takes as input a graph from networkx and provides the associated graph state." ] }, { "cell_type": "code", "execution_count": 7, "id": "ce985ada", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Create the graph with networkx\n", "G = nx.Graph()\n", "G.add_nodes_from([2,1,0,3])\n", "\n", "G.add_edge(0,1)\n", "G.add_edge(1,2)\n", "G.add_edge(2,0)\n", "G.add_edge(2,3)\n", "\n", "nx.draw_networkx(G, with_labels=True)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "7a0fcf0e", "metadata": {}, "source": [ "We choose the encoding type we want." ] }, { "cell_type": "code", "execution_count": 8, "id": "53c0bf45", "metadata": {}, "outputs": [], "source": [ "# Set the generator with the dual rail encoding\n", "generator = StateGenerator(Encoding.DUAL_RAIL)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "9b9148b8", "metadata": {}, "source": [ "Then we use the generator to create the graph state:" ] }, { "cell_type": "code", "execution_count": 9, "id": "6cc34dd9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.25*|0,1,1,0,1,0,1,0>+0.25*|1,0,0,1,0,1,0,1>+0.25*|1,0,1,0,1,0,1,0>+0.25*|1,0,0,1,1,0,1,0>-0.25*|0,1,1,0,0,1,1,0>-0.25*|0,1,0,1,1,0,1,0>+0.25*|1,0,1,0,0,1,1,0>-0.25*|1,0,0,1,0,1,1,0>-0.25*|0,1,0,1,0,1,1,0>+0.25*|0,1,1,0,1,0,0,1>+0.25*|1,0,1,0,1,0,0,1>-0.25*|0,1,0,1,1,0,0,1>+0.25*|1,0,0,1,1,0,0,1>+0.25*|0,1,1,0,0,1,0,1>-0.25*|1,0,1,0,0,1,0,1>+0.25*|0,1,0,1,0,1,0,1>\n" ] } ], "source": [ "graph_state = generator.graph_state(G)\n", "print(graph_state)" ] }, { "cell_type": "markdown", "id": "73f10fa51cd8de2d", "metadata": {}, "source": [ "You can use the generated state as an input state in any noiseless simulation" ] }, { "cell_type": "code", "execution_count": 10, "id": "2872df70be78a416", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|1,0,1,1,1,0,0,0> 0.018002
|0,0,0,1,2,1,0,0> 0.015395
|0,0,0,0,0,0,4,0> 0.015104
|0,0,0,0,1,3,0,0> 0.015061
|1,1,0,0,2,0,0,0> 0.014839
|1,0,0,1,0,0,0,2> 0.013904
|0,0,0,0,1,2,0,1> 0.011317
|0,1,0,1,0,0,2,0> 0.011303
|1,0,0,0,0,1,2,0> 0.011015
|1,2,0,0,0,1,0,0> 0.010701
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "p = pcvl.Processor(\"SLOS\", pcvl.Unitary(pcvl.Matrix.random_unitary(8))) # Use a 8x8 random unitary matrix as a circuit\n", "p.min_detected_photons_filter(4)\n", "p.with_input(graph_state)\n", "sampler = pcvl.algorithm.Sampler(p)\n", "pcvl.pdisplay(sampler.probs()['results'], max_v=10)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "96b76153", "metadata": {}, "source": [ "## References" ] }, { "attachments": {}, "cell_type": "markdown", "id": "44ca8b6a", "metadata": {}, "source": [ "[1]\n", "Marc Hein et al. “Entanglement in graph states and its applications”. In: *arXiv preprint\n", "quant-ph/0602096* (2006). https://arxiv.org/abs/quant-ph/0602096\n", "\n", "[2]\n", "https://qiskit.org/documentation/stubs/qiskit.visualization.plot_state_qsphere.html\n", "\n", "[3]\n", "https://nbviewer.org/urls/qutip.org/qutip-tutorials/tutorials-v4/visualization/qubism-and-schmidt-plots.ipynb\n", "\n", "\n", "[4]\n", "https://perceval.quandela.net/interopdocs/\n", "\n" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/LOv_rewriting_rules.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "dd3d8d19", "metadata": {}, "source": [ "# LOv rewriting rules\n", "\n", "The aim of this notebook is to rewrite a circuit using rewriting rules based on the article: *LOv-Calculus: A Graphical Language for Linear Optical Quantum Circuits*.\n", "\n", "We show how to use these rewriting rules to generate unique triangular normal forms." ] }, { "cell_type": "code", "execution_count": 1, "id": "96bdb88a", "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "from perceval.utils.algorithms.optimize import optimize\n", "from perceval.utils.algorithms.norm import frobenius\n", "import random\n", "\n", "from perceval.rendering import DisplayConfig, SymbSkin\n", "DisplayConfig.select_skin(SymbSkin)" ] }, { "cell_type": "markdown", "id": "ba837e96", "metadata": {}, "source": [ "This is the first rewrite rule used in this noteobok. It is the rule 37 in the article." ] }, { "cell_type": "markdown", "id": "09923a89", "metadata": {}, "source": [ "![rewrite37](../_static/img/rewrite37.png)" ] }, { "cell_type": "code", "execution_count": 2, "id": "ede320ce", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PATTERN1\n", "\n", "\n", "Φ=phi0\n", "\n", "\n", "Θ=theta1\n", "\n", "Rx\n", "\n", "\n", "Φ=phi2\n", "\n", "\n", "Φ=phi1\n", "\n", "\n", "Θ=theta2\n", "\n", "Rx\n", "\n", "\n", "Θ=theta3\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pattern1=pcvl.Circuit(3, name=\"pattern1\")//(0,pcvl.PS(pcvl.P(\"phi0\")))//(0,pcvl.BS(theta=pcvl.P(\"theta1\")))//(0,pcvl.PS(pcvl.P(\"phi2\")))//(1,pcvl.PS(pcvl.P(\"phi1\")))//(1,pcvl.BS(theta=pcvl.P(\"theta2\")))//(0,pcvl.BS(theta=pcvl.P(\"theta3\")))\n", "pattern1._color = \"lightgreen\"\n", "pcvl.pdisplay(pcvl.Circuit(3).add(0,pattern1,False), recursive=True)" ] }, { "cell_type": "code", "execution_count": 3, "id": "1e1ee73a", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "REWRITE\n", "\n", "\n", "Φ=beta2\n", "\n", "\n", "Φ=beta1\n", "\n", "\n", "Θ=alpha1\n", "\n", "Rx\n", "\n", "\n", "Θ=alpha2\n", "\n", "Rx\n", "\n", "\n", "Φ=beta3\n", "\n", "\n", "Θ=alpha3\n", "\n", "Rx\n", "\n", "\n", "Φ=beta4\n", "\n", "\n", "Φ=beta5\n", "\n", "\n", "Φ=beta6\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "0\n", "1\n", "2\n", "" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rewrite1=pcvl.Circuit(3, name=\"rewrite\")//(0,pcvl.PS(pcvl.P(\"beta2\")))//(1,pcvl.PS(pcvl.P(\"beta1\")))//(1,pcvl.BS(theta=pcvl.P(\"alpha1\")))//(0,pcvl.BS(theta=pcvl.P(\"alpha2\")))//(1,pcvl.PS(pcvl.P(\"beta3\")))//(1,pcvl.BS(theta=pcvl.P(\"alpha3\")))//(0,pcvl.PS(pcvl.P(\"beta4\")))//(1,pcvl.PS(pcvl.P(\"beta5\")))//(2,pcvl.PS(pcvl.P(\"beta6\")))\n", "rewrite1._color = \"lightgreen\"\n", "pcvl.pdisplay(pcvl.Circuit(3).add(0,rewrite1,False), recursive=True)" ] }, { "cell_type": "markdown", "id": "d4197839", "metadata": {}, "source": [ "Let us implement now the rule number 1." ] }, { "cell_type": "markdown", "id": "171cff3a", "metadata": {}, "source": [ "![rewrite1](../_static/img/rewrite1.png)" ] }, { "cell_type": "code", "execution_count": 4, "id": "5468fdb9", "metadata": {}, "outputs": [], "source": [ "pattern2=pcvl.Circuit(1, name=\"pattern2\")//pcvl.PS(pcvl.P(\"phi1\"))//pcvl.PS(pcvl.P(\"phi2\"))\n", "rewrite2=pcvl.Circuit(1, name=\"rewrite2\")//pcvl.PS(pcvl.P(\"phi\"))" ] }, { "cell_type": "code", "execution_count": 5, "id": "e428775f", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "PATTERN2\n", "\n", "\n", "Φ=phi1\n", "\n", "\n", "Φ=phi2\n", "\n", "0\n", "0\n", "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pcvl.pdisplay(pcvl.Circuit(1).add(0,pattern2,False), recursive=True)" ] }, { "cell_type": "code", "execution_count": 6, "id": "e2bdd9f4", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "REWRITE2\n", "\n", "\n", "Φ=phi\n", "\n", "0\n", "0\n", "" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pcvl.pdisplay(pcvl.Circuit(1).add(0,rewrite2,False), recursive=True)" ] }, { "cell_type": "markdown", "id": "7d547376", "metadata": {}, "source": [ "In fact, this rule has been directly implemented in Perceval, with the call of `simplify`." ] }, { "cell_type": "code", "execution_count": 7, "id": "49074c57", "metadata": {}, "outputs": [], "source": [ "from perceval.utils.algorithms.simplification import simplify" ] }, { "cell_type": "markdown", "id": "6ceae57c", "metadata": {}, "source": [ "The third rule used in this notebook is the following one:" ] }, { "cell_type": "markdown", "id": "246fc6ce", "metadata": {}, "source": [ "![rewrite33](../_static/img/rewrite33.png)" ] }, { "cell_type": "code", "execution_count": 8, "id": "d056ab6c", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PATTERN3\n", "\n", "\n", "Φ=phip\n", "\n", "\n", "Θ=theta\n", "\n", "Rx\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pattern3=pcvl.Circuit(2, name=\"pattern3\")//(1,pcvl.PS(pcvl.P(\"phip\")))//(0,pcvl.BS(theta=pcvl.P(\"theta\")))\n", "pattern3._color = \"pink\"\n", "pcvl.pdisplay(pcvl.Circuit(2).add(0,pattern3,False), recursive=True)" ] }, { "cell_type": "code", "execution_count": 9, "id": "6ef8057d", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "REWRITE3\n", "\n", "\n", "Φ=phi1\n", "\n", "\n", "Θ=theta\n", "\n", "Rx\n", "\n", "\n", "Φ=phi2\n", "\n", "\n", "Φ=phi3\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rewrite3=pcvl.Circuit(2, name=\"rewrite3\")//(0,pcvl.PS(pcvl.P(\"phi1\")))//(0,pcvl.BS(theta=pcvl.P(\"theta\")))//(0,pcvl.PS(pcvl.P(\"phi2\")))//(1,pcvl.PS(pcvl.P(\"phi3\")))\n", "rewrite3._color = \"pink\"\n", "pcvl.pdisplay(pcvl.Circuit(2).add(0,rewrite3,False), recursive=True)" ] }, { "cell_type": "markdown", "id": "6c7efe31", "metadata": {}, "source": [ "And the fourth rule is the rule 38 in the article." ] }, { "cell_type": "markdown", "id": "4cae9637", "metadata": {}, "source": [ "![rewrite38](../_static/img/rewrite38.png)" ] }, { "cell_type": "code", "execution_count": 10, "id": "597bc933", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PATTERN4\n", "\n", "Θ=theta1\n", "\n", "Rx\n", "\n", "\n", "Φ=phi1\n", "\n", "\n", "Θ=theta2\n", "\n", "Rx\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pattern4=pcvl.Circuit(2, name=\"pattern4\")//(0,pcvl.BS(theta=pcvl.P(\"theta1\")))//(0,pcvl.PS(pcvl.P(\"phi1\")))//(0,pcvl.BS(theta=pcvl.P(\"theta2\")))\n", "pattern4._color = \"orange\"\n", "pcvl.pdisplay(pcvl.Circuit(2).add(0,pattern4,False), recursive=True)" ] }, { "cell_type": "code", "execution_count": 11, "id": "1866e2c5", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "REWRITE4\n", "\n", "\n", "Φ=beta1\n", "\n", "\n", "Θ=alpha1\n", "\n", "Rx\n", "\n", "\n", "Φ=beta2\n", "\n", "\n", "Φ=beta3\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rewrite4=pcvl.Circuit(2, name=\"rewrite4\")//(0,pcvl.PS(pcvl.P(\"beta1\")))//(0,pcvl.BS(theta=pcvl.P(\"alpha1\")))//(0,pcvl.PS(pcvl.P(\"beta2\")))//(1,pcvl.PS(pcvl.P(\"beta3\")))\n", "rewrite4._color = \"orange\"\n", "pcvl.pdisplay(pcvl.Circuit(2).add(0,rewrite4,False), recursive=True)" ] }, { "cell_type": "code", "execution_count": 12, "id": "deafebff", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=0.714717\n", "\n", "\n", "Θ=0.63083\n", "\n", "Rx\n", "\n", "\n", "Φ=0.406342\n", "\n", "\n", "Θ=0.425317\n", "\n", "Rx\n", "\n", "\n", "Φ=0.566162\n", "\n", "\n", "Θ=0.30391\n", "\n", "Rx\n", "\n", "\n", "Φ=0.554753\n", "\n", "\n", "Θ=0.903968\n", "\n", "Rx\n", "\n", "\n", "Φ=0.930902\n", "\n", "\n", "Θ=0.235299\n", "\n", "Rx\n", "\n", "\n", "Φ=0.858201\n", "\n", "Θ=0.336429\n", "\n", "Rx\n", "\n", "\n", "Φ=0.687349\n", "\n", "\n", "Θ=0.181449\n", "\n", "Rx\n", "\n", "\n", "Φ=0.400677\n", "\n", "\n", "Θ=0.271942\n", "\n", "Rx\n", "\n", "\n", "Φ=0.800875\n", "\n", "Θ=0.464146\n", "\n", "Rx\n", "\n", "\n", "Φ=0.368609\n", "\n", "\n", "Θ=0.198345\n", "\n", "Rx\n", "\n", "\n", "Φ=0.82193\n", "\n", "\n", "Θ=0.029511\n", "\n", "Rx\n", "\n", "\n", "Φ=0.459109\n", "\n", "Θ=0.101356\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a=pcvl.GenericInterferometer(4, lambda idx:pcvl.Circuit(2)//pcvl.PS(phi=random.random())//pcvl.BS(theta=random.random()), depth=8, shape=pcvl.InterferometerShape.RECTANGLE)\n", "pcvl.pdisplay(a, recursive=True, render_size=0.7)" ] }, { "cell_type": "markdown", "id": "f13dd7d7", "metadata": {}, "source": [ "## Normalizing Circuit" ] }, { "cell_type": "code", "execution_count": 13, "id": "0f5ee748", "metadata": {}, "outputs": [], "source": [ "import drawsvg as draw" ] }, { "cell_type": "code", "execution_count": 14, "id": "df109c0f", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=3.759963\n", "\n", "\n", "Φ=3.750786\n", "\n", "\n", "Θ=12.052413\n", "\n", "Rx\n", "\n", "\n", "Θ=6.629598\n", "\n", "Rx\n", "\n", "\n", "Φ=1.127343\n", "\n", "\n", "Φ=4.318821\n", "\n", "\n", "Θ=4.433226\n", "\n", "Rx\n", "\n", "\n", "Φ=1.063861\n", "\n", "\n", "Φ=4.678118\n", "\n", "\n", "Θ=6.51914\n", "\n", "Rx\n", "\n", "Θ=5.359688\n", "\n", "Rx\n", "\n", "\n", "Φ=3.764987\n", "\n", "\n", "Φ=6.21441\n", "\n", "\n", "Θ=12.399699\n", "\n", "Rx\n", "\n", "\n", "Φ=0.89935\n", "\n", "\n", "Φ=3.124728\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "matching pattern pattern3\n", "matching pattern pattern4\n", "matching pattern pattern1\n", "matching pattern pattern3\n", "matching pattern pattern4\n" ] } ], "source": [ "reverse = []\n", "direct=[]\n", "def draw_frame(a):\n", " if isinstance(a, pcvl.Circuit):\n", " d = pcvl.pdisplay(a, recursive=True, render_size=0.6)\n", " reverse.insert(0, d)\n", " direct.append(d)\n", " return d\n", " return a\n", "\n", "rules = [(pattern1, rewrite1, \"lightgreen\"), # (pattern2, rewrite2, \"lightblue\"),\n", " (pattern3, rewrite3, \"pink\"), (pattern4, rewrite4, \"orange\")]\n", "\n", "with draw.frame_animate_jupyter(draw_frame, delay=0.1) as anim:\n", " anim.draw_frame(a)\n", " while True:\n", " found = False\n", " for pattern, rewrite, color in rules:\n", " start_pos = 0\n", " while True:\n", " print(\"matching pattern\", pattern.name)\n", " matched = a.match(pattern, browse=True, pos=start_pos)\n", " if matched is None:\n", " break\n", " print(\"matching ok\", matched.v_map)\n", " idx = a.isolate(list(matched.pos_map.keys()), color=color)\n", " anim.draw_frame(a)\n", " for k, v in matched.v_map.items():\n", " pattern.param(k).set_value(v)\n", " v = pattern.compute_unitary()\n", " print(\"optimizing rewrite\",rewrite.name)\n", " res = optimize(rewrite, v, frobenius, sign=-1)\n", " print(\"found params with distance\", res.fun)\n", " subc = rewrite.copy()\n", " found = True\n", " a.replace(idx, subc, merge=False)\n", " anim.draw_frame(a)\n", " a.replace(idx, subc, merge=True)\n", " pattern.reset_parameters()\n", " rewrite.reset_parameters()\n", " a = simplify(a)\n", " anim.draw_frame(a)\n", " start_pos = idx\n", " if not found:\n", " break" ] }, { "cell_type": "markdown", "id": "954dfac2", "metadata": {}, "source": [ "This representation is exactly the normal form that we wanted to obtain !" ] }, { "cell_type": "markdown", "id": "c174aa4c", "metadata": {}, "source": [ "## Reference\n", "\n", "> A. Clément, N. Heurtel, S. Mansfield, S. Perdrix, B. Valiron. LOv-Calculus: A Graphical Language for Linear Optical Quantum Circuits, 47th International Symposium on Mathematical Foundations of Computer Science [MFCS](https://doi.org/10.4230/LIPIcs.MFCS.2022.35), 35:1--35:16 (2022)." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/MPS_techniques_for_boson_sampling.ipynb ================================================ { "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# MPS techniques for Boson Sampling\n", "\n", "In this notebook, we explain how to use the MPS (Matrix Product State) backend to simulate a linear circuit. MPS simulation is based on a type of tensor network simulation, which gives an approximation of the output states [[1]](#References) [[2]](#References). It does the computation on each component of the circuits one-by-one, and not on the whole unitary. The states are represented by tensors, which are then updated at each component. These tensors can be seen as a big set of matrices, and the approximation is done by choosing the dimension of these matrices, called the *bond dimension*. For this example, we simulate a simple boson sampling problem, with 6 modes and 3 photons [[3]](#References)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "import matplotlib.pyplot as plt" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Definition of the circuit\n", "\n", "Just like in the Boson Sampling notebook, we generate a Haar-random unitary and its decomposition in a circuit :" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6 modes triangular Boson Sampler :\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=0.682353\n", "\n", "\n", "Φ=1.839246\n", "\n", "\n", "Φ=5.325111\n", "\n", "\n", "Φ=1.540778\n", "\n", "\n", "Φ=6.134872\n", "\n", "\n", "Φ=5.133685\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.077922\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.115285\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.773545\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.310632\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.118361\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.116664\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.298164\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.383843\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.603772\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.871176\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.91304\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.896901\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.873668\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.198433\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.864696\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.929913\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.252653\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.269561\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.223128\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.226287\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.467155\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.570584\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.129614\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.067041\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.169704\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.33072\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.346625\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.247317\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.549437\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.853279\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = 6\n", "unitary = pcvl.Matrix.random_unitary(m)\n", "mzi = (pcvl.BS() // (0, pcvl.PS(phi=pcvl.Parameter(\"φ_a\")))\n", " // pcvl.BS() // (1, pcvl.PS(phi=pcvl.Parameter(\"φ_b\"))))\n", "linear_circuit = pcvl.Circuit.decomposition(unitary, mzi,\n", " phase_shifter_fn=pcvl.PS,\n", " shape=pcvl.InterferometerShape.TRIANGLE)\n", "\n", "print(m, \" modes triangular Boson Sampler :\")\n", "pcvl.pdisplay(linear_circuit, compact = True)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## MPS simulation\n", "\n", "Let us now define the MPS simulation, using the MPS backend in Perceval." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "mps = pcvl.MPSBackend()\n", "mps.set_circuit(linear_circuit)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We now choose the size of the matrices (the bond dimension) for our simulation." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "chi = 8\n", "mps.set_cutoff(chi)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "And finally, we get the output probability distribution from a given input state with 3 photons." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,0,0,0,1,2> 0.078508
|0,0,0,0,0,3> 0.053524
|2,1,0,0,0,0> 0.051962
|0,0,0,0,2,1> 0.042042
|3,0,0,0,0,0> 0.036251
|0,0,1,1,0,1> 0.033647
|1,0,1,0,1,0> 0.032665
|1,1,0,0,0,1> 0.032312
|1,2,0,0,0,0> 0.028953
|2,0,0,0,1,0> 0.028039
|0,1,2,0,0,0> 0.027845
|0,1,1,1,0,0> 0.026747
|0,0,1,0,2,0> 0.02671
|0,0,0,0,3,0> 0.026532
|1,0,1,0,0,1> 0.026327
|1,0,0,1,1,0> 0.024339
|0,0,2,1,0,0> 0.023886
|1,0,0,0,0,2> 0.023375
|1,1,0,1,0,0> 0.021942
|1,0,0,1,0,1> 0.021592
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "n = 3\n", "input_state = pcvl.BasicState([1]*n + [0]*(m-n))\n", "mps.set_input_state(input_state)\n", "probs = mps.prob_distribution()\n", "pcvl.pdisplay(probs, max_v=20)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Certification of the MPS method\n", "\n", "As we make an approximation by choosing the bond dimension, we have to check when does this approximation becomes good enough. Unfortunately, there is no formula giving the minimal size for a given approximation error. What we can do though is to compute the *Total Variance Distance* (TVD) between an ideal simulation of Boson Sampling, and an approximated one. To compute the ideal one, we can for instance use the *SLOS* backend on Perceval :" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,0,0,0,1,2> 0.078508
|0,0,0,0,0,3> 0.053524
|2,1,0,0,0,0> 0.051962
|0,0,0,0,2,1> 0.042042
|3,0,0,0,0,0> 0.036251
|0,0,1,1,0,1> 0.033647
|1,0,1,0,1,0> 0.032665
|1,1,0,0,0,1> 0.032312
|1,2,0,0,0,0> 0.028953
|2,0,0,0,1,0> 0.028039
|0,1,2,0,0,0> 0.027845
|0,1,1,1,0,0> 0.026747
|0,0,1,0,2,0> 0.02671
|0,0,0,0,3,0> 0.026532
|1,0,1,0,0,1> 0.026327
|1,0,0,1,1,0> 0.024339
|0,0,2,1,0,0> 0.023886
|1,0,0,0,0,2> 0.023375
|1,1,0,1,0,0> 0.021942
|1,0,0,1,0,1> 0.021592
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "slos = pcvl.SLOSBackend()\n", "slos.set_circuit(pcvl.Unitary(unitary))\n", "slos.set_input_state(input_state)\n", "probs_slos = slos.prob_distribution()\n", "pcvl.pdisplay(probs_slos, max_v=20)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We also have to define the TVD function." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def tvd(probs1, probs2):\n", " tvd = 0\n", " for state, prob in probs1.items():\n", " tvd += abs(prob - probs2[state])\n", " return tvd\n" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Now we compute the TVD between the two simulations for different bond dimensions, going from 1 to 10." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAl4AAAHHCAYAAABuoFaQAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy80BEi2AAAACXBIWXMAAA9hAAAPYQGoP6dpAABijklEQVR4nO3deVwU9f8H8NfuAruAXIJcgqCAkhcoKqECHhSaR1qmlopaWimZRlZSv7RTM4/8mmeW5p1laaVpKpp3ahoepYgHigeXyqly7H5+f+BurtwKO7D7ej4e+3jI7Gdm3zvrzrx25jOfkQkhBIiIiIioxsmlLoCIiIjIVDB4ERERERkIgxcRERGRgTB4ERERERkIgxcRERGRgTB4ERERERkIgxcRERGRgTB4ERERERkIgxcRERGRgRht8JLJZPjggw+qdZnffvstZDIZkpKSqnW51W3GjBlo0qQJFAoFAgMDqzz/H3/8AZlMhvXr11d/cXXIBx98AJlMhoyMDKlLISPk7e2NESNGSF1GndKlSxe0bNmy2paXlJQEmUyGmTNnVthWuz2434OfoXbb+ccff1RbjVWhfT/ffvutblppddeULl26oEuXLrq/Db0vGTFiBLy9vQ3yWo+iRoOXNqiU9fjzzz9r8uUf2tSpU7Fx40apy3go27Ztw9tvv41OnTph2bJlmDp1aplt16xZgzlz5hiuuPt06dKl3P8b2kffvn0hk8nwf//3f2UuKzExETKZDDExMQD+29BoH1ZWVmjUqBH69OmDZcuWIT8/31BvswQp17mhJCUlYeTIkfDx8YFKpYKrqyvCwsIwZcoUvXaV3YneuHEDb731Fpo1awaVSoX69esjMjISmzZtKrV9eno6xo8fD39/f1haWsLZ2RkdOnTAO++8g9zc3Gp5j4ak/X88atSoUp9/7733dG3u/5EwYsQIve+Bra0tAgICMGvWrBLfgX379qFnz55o2LAhVCqV7vuyZs2aGn1vpqAufuevXbuGDz74APHx8VKXUkJtrq2yzAzxIh999BEaN25cYrqvr68hXr7Kpk6digEDBqBfv35604cNG4bBgwdDqVRKU1gl7Ny5E3K5HN988w0sLCzKbbtmzRqcOnUKEyZMMExx93nvvff0diRHjhzB3Llz8e677+Kxxx7TTW/dujUSExOxdu1afPLJJ6UuS7tzGDp0qN70hQsXol69esjPz8fVq1fx+++/48UXX8ScOXOwadMmeHp61sA7K5+U69wQzp07h/bt28PS0hIvvvgivL29cf36dRw7dgzTp0/Hhx9+WKXlJSQkoHv37khPT8fIkSPRrl07ZGZmYvXq1ejTpw8mTpyIGTNm6NrfvHkT7dq1Q3Z2Nl588UX4+/vjxo0bOHHiBBYuXIgxY8agXr161f22a5xKpcKPP/6IBQsWlPher127FiqVCnfv3i0xn1KpxNdffw0AyMzMxI8//oiJEyfiyJEj+O677wAAP/zwAwYNGoTAwECMHz8eDg4OuHjxIvbs2YMlS5bghRdeqPk3WAf83//9HyZNmlRum7CwMNy5c0fvM5L6O1+Zuh907do1fPjhh/D29q7SWZNt27ZVsbqqK6+2JUuWQKPR1HgNj8ogwatnz55o166dIV6qRikUCigUCqnLKFdaWhosLS0rDF1Se+KJJ/T+VqlUmDt3Lp544gm9Q9UAMGTIELz//vv4888/8fjjj5dY1tq1a+Hv74+2bdvqTR8wYACcnJx0f0+ePBmrV69GVFQUnnvuuVp7xLUu++KLL5Cbm4v4+Hh4eXnpPZeWllalZRUWFmLAgAG4desW9uzZg+DgYN1zb7zxBoYMGYKZM2eiXbt2GDRoEADgm2++weXLl7F//3507NhRb3nZ2dm1/ntRlh49euCXX37Bli1b8PTTT+umHzhwABcvXsSzzz6LH3/8scR8ZmZmej9Ixo4di+DgYKxbtw6zZ8+Gu7s7PvjgAzRv3hx//vlnifVT1c+sqvLy8mBtbV2jr1FdzMzMYGZW/i5TLpdDpVIZqKLKqUzdj+r27duwsrKS/Ptlbm4u6etXluR9vAoLC1G/fn2MHDmyxHPZ2dlQqVSYOHGiblpaWhpeeukluLi4QKVSISAgAMuXL6/wdco69/vg+W+ZTIa8vDwsX75cd4heew6/rD5eCxYsQIsWLaBUKuHu7o7o6GhkZmbqtdGeVvn333/RtWtXWFlZoWHDhvj8888rrB0AioqK8PHHH8PHxwdKpRLe3t5499139U4ZyGQyLFu2DHl5ebra7z/X/2A9mzdvxqVLl3RtH1w/Go0Gn376KTw8PKBSqdC9e3ecO3euxLIOHTqEHj16wM7ODlZWVggPD8f+/fsr9b4qY8iQIQBQ6mmPo0ePIiEhQdemMssaNWoUDh06hO3bt1dqnoyMDAwcOBC2trZwdHTE+PHjSz26sGrVKgQFBcHS0hL169fH4MGDkZycrHu+rHUuhICTk5PuVClQvO7t7e2hUCj0/i9Nnz4dZmZmeqfMzpw5gwEDBqB+/fpQqVRo164dfvnllxL1ZWZmYsKECfD09IRSqYSvry+mT5+u9wvx/j4vX331le7/W/v27XHkyJEK19X58+fh4eFRInQBgLOzc4Xz3+/HH3/EqVOnMGnSJL3QBRT/CFq8eDHs7e31+nKeP38eCoWi1IBua2tb4U7x0qVLGDt2LJo1awZLS0s4OjriueeeK/Gd124L9u/fj5iYGDRo0ADW1tbo378/0tPT9doKIfDJJ5/Aw8MDVlZW6Nq1K/75558qrYuGDRsiLCysxHdg9erVaNWqVaX7Pcnlct0PG+17On/+PNq3b1/qTrMyn5m3tzd69+6Nbdu2ITAwECqVCs2bN8dPP/2k1067znbv3o2xY8fC2dkZHh4euucrsx3VOnr0KDp27AhLS0s0btwYixYt0nu+oKAAkydPRlBQEOzs7GBtbY3Q0FDs2rWrzPfxxRdfwMvLC5aWlggPD8epU6f0nq9MX6kH+3iV9Z3Pzc2FtbU1xo8fX2IZV65cgUKhwLRp08p9rczMTIwYMQJ2dnawt7fH8OHDS11fpdW9fft2dO7cGfb29qhXrx6aNWuGd999V/ce2rdvDwAYOXJkiX2Jdl929OhRhIWFwcrKSjfvg328tNRqNd599124urrC2toaffv21ds2AmX3ebx/mRXVVtp+Pi8vD2+++aZuu9esWTPMnDkTQgi9djKZDK+99ho2btyIli1bQqlUokWLFti6dateu5ycHEyYMAHe3t5QKpVwdnbGE088gWPHjpWovSwGCV5ZWVnIyMjQe9y4cQNAcULt378/Nm7ciIKCAr35Nm7ciPz8fAwePBgAcOfOHXTp0gUrV67EkCFDMGPGDNjZ2WHEiBH43//+Vy21rly5EkqlEqGhoVi5ciVWrlyJV155pcz2H3zwAaKjo+Hu7o5Zs2bh2WefxeLFi/Hkk0+isLBQr+2tW7fQo0cPXT8Lf39/vPPOO9iyZUuFdY0aNQqTJ09G27Zt8cUXXyA8PBzTpk3TrRtt7aGhoVAqlbraw8LCSl3ee++9h8DAQDg5OenaPtgP4bPPPsOGDRswceJExMbG4s8//ywRcHbu3ImwsDBkZ2djypQpmDp1KjIzM9GtWzccPny4wvdVGY0bN0bHjh3x/fffQ61W6z2n3RFV5XTIsGHDAFT+sPjAgQNx9+5dTJs2DU899RTmzp2Ll19+Wa/Np59+iqioKPj5+WH27NmYMGEC4uLiEBYWptsYlrXOZTIZOnXqhD179uiWd+LECWRlZQGAXojdu3cv2rRpoztd9s8//+Dxxx/H6dOnMWnSJMyaNQvW1tbo168fNmzYoJvv9u3bCA8Px6pVqxAVFYW5c+eiU6dOiI2N1Qt8WmvWrMGMGTPwyiuv4JNPPkFSUhKeeeaZEv+nH+Tl5YXk5GTs3LmzUuu2PL/++isAICoqqtTn7ezs8PTTT+PMmTO6HwReXl5Qq9VYuXLlQ73mkSNHcODAAQwePBhz587Fq6++iri4OHTp0gW3b98u0X7cuHE4fvw4pkyZgjFjxuDXX3/Fa6+9ptdm8uTJeP/99xEQEKC78OXJJ59EXl5elWp74YUX8Ouvv+pCd1FREX744Ycqnwo8f/48AMDR0RFA8TqLi4vDlStXqrSc+yUmJmLQoEHo2bMnpk2bBjMzMzz33HOl/rgZO3Ys/v33X0yePFl3Cqyq29GnnnoKQUFB+Pzzz+Hh4YExY8Zg6dKlujbZ2dn4+uuv0aVLF0yfPh0ffPAB0tPTERkZWWrfoBUrVmDu3LmIjo5GbGwsTp06hW7duiE1NfWh1wlQ9ne+Xr166N+/P9atW1dim7Z27VoIIcr9MSmEwNNPP42VK1di6NCh+OSTT3DlyhUMHz68wpr++ecf9O7dG/n5+fjoo48wa9Ys9O3bV7edeeyxx/DRRx8BAF5++eVS9yU3btxAz549ERgYiDlz5qBr167lvuann36KzZs345133sHrr7+O7du3IyIiAnfu3Kmw3vtVprb7CSHQt29ffPHFF+jRowdmz56NZs2a4a233ip1u7dv3z6MHTsWgwcPxueff467d+/i2Wef1eUVAHj11VexcOFCPPvss1iwYAEmTpwIS0tLnD59uvJvRNSgZcuWCQClPpRKpa7d77//LgCIX3/9VW/+p556SjRp0kT395w5cwQAsWrVKt20goICERISIurVqyeys7N10wGIKVOm6P4ePny48PLyKlHjlClTxIOrwdraWgwfPrzM93Px4kUhhBBpaWnCwsJCPPnkk0KtVuvazZs3TwAQS5cu1U0LDw8XAMSKFSt00/Lz84Wrq6t49tlnS7zW/eLj4wUAMWrUKL3pEydOFADEzp079d6ntbV1ucvT6tWrV6nrZNeuXQKAeOyxx0R+fr5u+v/+9z8BQJw8eVIIIYRGoxF+fn4iMjJSaDQaXbvbt2+Lxo0biyeeeKJSdQghxA8//CAAiF27dpX6/Pz58wUA8fvvv+umqdVq0bBhQxESEqLXVvuZpqenl7qsW7duCQCif//+5dakXU7fvn31po8dO1YAEMePHxdCCJGUlCQUCoX49NNP9dqdPHlSmJmZ6U0va53PmDFDKBQK3f/huXPnCi8vL9GhQwfxzjvv6N6vvb29eOONN3Tzde/eXbRq1UrcvXtXN02j0YiOHTsKPz8/3bSPP/5YWFtbi7Nnz+q97qRJk4RCoRCXL18WQghx8eJFAUA4OjqKmzdv6tr9/PPPpX5HH3Tq1ClhaWkpAIjAwEAxfvx4sXHjRpGXl1eibXh4uGjRokWZywoMDBR2dnblvt7s2bMFAPHLL78IIYRISUkRDRo0EACEv7+/ePXVV8WaNWtEZmZmucvRun37dolpBw8eLPHd1W4LIiIi9P7vv/HGG0KhUOheT7uN6NWrl167d999VwAodTvzIAAiOjpa3Lx5U1hYWIiVK1cKIYTYvHmzkMlkIikpqdT/89ptQXp6ukhPTxfnzp0TU6dOFTKZTLRu3VrX7ptvvhEAhIWFhejatat4//33xd69e/W2aeXx8vISAMSPP/6om5aVlSXc3NxEmzZtSqyzzp07i6KiIt30h9mOzpo1SzctPz9fBAYGCmdnZ1FQUCCEEKKoqEhv2yVE8ffexcVFvPjii7pp2v/vlpaW4sqVK7rphw4dEgD0vmul7Su8vLz0PkPttvP+7VhZ33ntfm/Lli1601u3bi3Cw8NLtL/fxo0bBQDx+eef66YVFRWJ0NBQAUAsW7aszLq/+OKLcrePQghx5MiREsvR0n4GixYtKvW5+2vXro+GDRvq7Z+///57AUD873//0017cF2WtczyantwP69dT5988oleuwEDBgiZTCbOnTunm6b9Dtw/7fjx4wKA+PLLL3XT7OzsRHR0dInXrgqDHPGaP38+tm/frve4/yhPt27d4OTkhHXr1umm3bp1C9u3b9f13QCA3377Da6urnj++ed108zNzfH6668jNzcXu3fvNsTb0dmxYwcKCgowYcIEyOX/rcrRo0fD1tYWmzdv1mtfr149vf4WFhYW6NChAy5cuFDu6/z2228AUCKhv/nmmwBQ4nWqy8iRI/VOP4SGhgKArt74+HgkJibihRdewI0bN3RHM/Py8tC9e3fs2bOn2jo6Dho0CObm5nqnWnbv3o2rV69W+jSjlvZoUU5OTqXaR0dH6/09btw4AP99Lj/99BM0Gg0GDhyod1TX1dUVfn5+5Z7e0AoNDYVarcaBAwcAFB/ZCg0NRWhoKPbu3QsAOHXqFDIzM3Wfw82bN7Fz504MHDgQOTk5ekeTIyMjkZiYiKtXrwIo7kAdGhoKBwcHvRojIiKgVqv1jrYBxevbwcFBrz4AFf5fbdGiBeLj4zF06FAkJSXhf//7H/r16wcXFxcsWbKkwvVwv5ycHNjY2JTbRvt8dnY2AMDFxQXHjx/Hq6++ilu3bmHRokV44YUX4OzsjI8//rjE6YUHWVpa6v5dWFiIGzduwNfXF/b29qWeSnj55Zf1TuNoP8dLly4B+G8bMW7cOL12D9PR2sHBAT169MDatWsBFB+V7NixY6mndbXy8vLQoEEDNGjQAL6+vnj33XcREhKidzT0xRdfxNatW9GlSxfs27cPH3/8MUJDQ+Hn56f7/1gRd3d39O/fX/e3ra0toqKi8PfffyMlJUWv7ejRo/X6ylZ1O2pmZqZ3FsLCwgKvvPIK0tLScPToUQDFp6K12y6NRoObN2+iqKgI7dq1K/Vz7NevHxo2bKj7u0OHDggODtZ9x2tCREQE3N3dsXr1at20U6dO4cSJEyUuFHrQb7/9BjMzM4wZM0Y3TaFQ6LZN5bG3twcA/Pzzzw+9fVYqlaV2DypLVFSU3nd5wIABcHNzq9H1CxSvJ4VCgddff11v+ptvvgkhRImzTREREfDx8dH93bp1a9ja2upt9+zt7XHo0CFcu3btoesySPDq0KEDIiIi9B73H5o0MzPDs88+i59//lnXZ+mnn35CYWGhXvC6dOkS/Pz89L6cAHRXwWk3doaifb1mzZrpTbewsECTJk1K1OPh4VHiXLuDgwNu3bpV4evI5fISV4G6urrC3t6+xt53o0aN9P7W7oi19SYmJgIAhg8frtu4ax9ff/018vPzdafLHpWjoyMiIyOxYcMGXf+qNWvWwMzMDAMHDqzSsrSnairaqWv5+fnp/e3j4wO5XK7rI5OYmAghBPz8/Eqsh9OnT1eqg3Lbtm1hZWWlC1na4BUWFoa//voLd+/e1T3XuXNnAMVXEAoh8P7775d4Xe3QDdrXTkxMxNatW0u0i4iI0GunVdFnX56mTZti5cqVyMjIwIkTJzB16lSYmZnh5Zdfxo4dOyqcX8vGxqbCcKx9/v7P0s3NDQsXLsT169eRkJCAuXPnokGDBpg8eTK++eabcpd3584dTJ48WdcfxMnJCQ0aNEBmZmap/5crWk/a7+aD/4caNGigF2wr64UXXsD27dtx+fJlbNy4scLTjCqVSvdjd8+ePUhOTsb+/fvRpEkTvXaRkZH4/fffkZmZiT179iA6OhqXLl1C7969K/X/19fXt8S2rWnTpgBQon/cg1e4V3U76u7uXqJDfmmvtXz5crRu3RoqlQqOjo5o0KABNm/eXOrn+ODno11mTY7ZKJfLMWTIEGzcuFF3Gnv16tVQqVR47rnnyp330qVLcHNzK3GF7oPrsDSDBg1Cp06dMGrUKLi4uGDw4MH4/vvvqxTCGjZsWKWO9A+uX5lMBl9f3xofE/PSpUtwd3cvsa0vKzM8+H0GSu6jP//8c5w6dQqenp7o0KEDPvjggwp/kD7IIFc1VsbgwYOxePFibNmyBf369cP3338Pf39/BAQEVMvyy+oU+eD59ZpU1hWRFf0K1zLUIHhaFdWr/aLOmDGjzEuOq/PS/aFDh2LTpk3YtGkT+vbtix9//BFPPvkkGjRoUKXlaDvNPuxwJg9+DhqNBjKZDFu2bCl1nVVmHZibmyM4OBh79uzBuXPnkJKSgtDQULi4uKCwsBCHDh3C3r174e/vr3u/2vU/ceJEREZGlrpc7XvUaDR44okn8Pbbb5faTrvj0nrU/6vaZbRq1QqtWrVCSEgIunbtitWrV+vCXkUee+wxxMfH4/Lly6VuEIHivnAA0Lx58xLPyWQyNG3aFE2bNkWvXr3g5+eH1atXlzkeFlB8NHPZsmWYMGECQkJCYGdnB5lMhsGDB5e6Y6qO9VQVffv2hVKpxPDhw5Gfn1/hjw6FQlHp9Q0AVlZWuiOtTk5O+PDDD7Fly5ZK9R2qrPuPKtaUVatWYcSIEejXrx/eeustODs76zqsa/u41QZRUVGYMWMGNm7ciOeffx5r1qxB7969YWdnV2OvaWlpiT179mDXrl3YvHkztm7dinXr1qFbt27Ytm1bpa7cr4nPsLx9tKFGE6jM93ngwIEIDQ3Fhg0bsG3bNsyYMQPTp0/HTz/9hJ49e1bqdWpN8AoLC4ObmxvWrVuHzp07Y+fOnXjvvff02nh5eeHEiRPQaDR6R73OnDmje74sDg4OpV7xUdrRosoGHO3rJSQk6P2CLCgowMWLF6u0wavodTQaDRITE/XGuEpNTUVmZma577s8jxrktIdkbW1tq+29lqdv376wsbHBmjVrYG5ujlu3blX5NCMAXcfrssLKgxITE/V+pZ87dw4ajUZ39YyPjw+EEGjcuHGJAPOg8tZ5aGgopk+fjh07dsDJyQn+/v6QyWRo0aIF9u7di71796J379669tr/c+bm5hWufx8fH+Tm5hrkcyqNdjiZ69evV3qe3r17Y+3atVixYkWpA+hmZ2fj559/hr+/f4UhukmTJnBwcKjw9devX4/hw4dj1qxZuml3794t8+q6imi/m4mJiXrbiPT09EodPXyQpaUl+vXrh1WrVqFnz556w6VUt6p8Ztqjr/f//z579iwAVDiSeFW3o9euXSsxDMWDr7V+/Xo0adIEP/30k15NDw7iq6U9en+/s2fPVsso6OV951u2bIk2bdpg9erV8PDwwOXLl/Hll19WuEztBRG5ubl6P+wSEhIqVZNcLkf37t3RvXt3zJ49G1OnTsV7772HXbt2ISIiotp/5D+4foUQOHfuHFq3bq2bVt4++v7/F1WpzcvLCzt27CjRbaEymaE8bm5uGDt2LMaOHYu0tDS0bdsWn376aaWDl+TDSWjJ5XIMGDAAv/76K1auXImioiK904wA8NRTTyElJUWvL1hRURG+/PJL1KtXD+Hh4WUu38fHB1lZWbpfyEDxBuX+vg5a1tbWldrQRkREwMLCAnPnztVLxN988w2ysrLQq1evCpdRGU899RQAlLjqcPbs2QDw0K9jbW39SKcCg4KC4OPjg5kzZ5Y6IviDl9U/KktLS/Tv3x+//fYbFi5cCGtra70xjSpjzZo1+PrrrxESEoLu3btXap758+fr/a3dMGq/ZM888wwUCgU+/PDDEkc6hBB6V8SUt85DQ0ORn5+POXPmoHPnzroNjPYK22vXrun6WgHFl/p36dIFixcvLnXneP/6HzhwIA4ePIjff/+9RLvMzEwUFRWVuw4qa+/evaVe+ajty1GZUyFaAwYMQPPmzfHZZ5/hr7/+0ntOo9FgzJgxuHXrlt7O9NChQ6VeLXj48GHcuHGjwtdXKBQlPsMvv/zyoY+MR0REwNzcHF9++aXech9lJPOJEydiypQpeP/99x96GfeLi4srdXpVPrNr167pbUuzs7OxYsUKBAYGwtXVtdx5q7odLSoqwuLFi3V/FxQUYPHixWjQoAGCgoIA/Hfk4v7lHTp0CAcPHiy1ho0bN+r6QwLF/18OHTpU6R1peSrazg4bNgzbtm3DnDlz4OjoWKnXfOqpp1BUVISFCxfqpqnV6kqFtps3b5aYpj1joe3qow21D/uD40ErVqzQ6zawfv16XL9+Xe+9+vj44M8//9Qb3WDTpk0lhp2oSm1PPfUU1Go15s2bpzf9iy++gEwmq/Lnq1arS3yWzs7OcHd3r9IdUQxyxGvLli26hHm/jh076iXZQYMG4csvv8SUKVPQqlUrvaM7QHFH1sWLF2PEiBE4evQovL29sX79euzfvx9z5swpt8/O4MGD8c4776B///54/fXXcfv2bSxcuBBNmzYt0dkyKCgIO3bs0A0w2Lhx4xLjCAHF/TRiY2Px4YcfokePHujbty8SEhKwYMECtG/fvsIOkpUVEBCA4cOH46uvvkJmZibCw8Nx+PBhLF++HP369avwUt6yBAUFYd26dYiJiUH79u1Rr1499OnTp9Lzy+VyfP311+jZsydatGiBkSNHomHDhrh69Sp27doFW1tb3ZAA1WXo0KFYsWIFfv/9dwwZMqTcwRfXr1+PevXqoaCgQDdy/f79+xEQEIAffvih0q958eJF9O3bFz169MDBgwexatUqvPDCC7rT4D4+Pvjkk08QGxuLpKQk9OvXDzY2Nrh48SI2bNiAl19+WTcWXXnrPCQkBGZmZkhISNAbriIsLEy3gb0/eAHFobBz585o1aoVRo8ejSZNmiA1NRUHDx7ElStXcPz4cQDAW2+9hV9++QW9e/fGiBEjEBQUhLy8PJw8eRLr169HUlJStRw9mT59Oo4ePYpnnnlG92v22LFjWLFiBerXr1+iU3l6enqpdyRo3LgxhgwZgvXr16N79+7o3Lmz3sj1a9aswbFjx/Dmm2+WGFJl9erV6N+/P4KCgmBhYYHTp09j6dKlUKlUuvGGytK7d2+sXLkSdnZ2aN68OQ4ePIgdO3bohl6oqgYNGmDixImYNm0aevfujaeeegp///03tmzZ8tDrOyAgoNq6YADA008/jcaNG6NPnz7w8fFBXl4eduzYgV9//RXt27ev1DahadOmeOmll3DkyBG4uLhg6dKlSE1NxbJlyyqct6rbUXd3d0yfPh1JSUlo2rQp1q1bh/j4eHz11Ve6ATR79+6Nn376Cf3790evXr1w8eJFLFq0CM2bNy/1R6Kvry86d+6MMWPG6H78ODo6lnlqvioq2s6+8MILePvtt7FhwwaMGTOmUoOA9unTB506dcKkSZOQlJSkGzetMj+kP/roI+zZswe9evWCl5cX0tLSsGDBAnh4eOj6j/r4+MDe3h6LFi2CjY0NrK2tERwcXOodaCqjfv36uu9wamoq5syZA19fX4wePVrXZtSoUVi/fj169OiBgQMH4vz581i1apVeZ/eq1tanTx907doV7733HpKSkhAQEIBt27bh559/xoQJE0osuyI5OTnw8PDAgAEDEBAQgHr16mHHjh04cuSI3lHyCj3SNZEVKG84CZRyOahGoxGenp6lXv6plZqaKkaOHCmcnJyEhYWFaNWqVamXleKB4SSEEGLbtm2iZcuWwsLCQjRr1kysWrWq1EuEz5w5I8LCwnSXxWsvcX1wOAmtefPmCX9/f2Fubi5cXFzEmDFjxK1bt/TalHXpfFnDXDyosLBQfPjhh6Jx48bC3NxceHp6itjYWL1hBLTLq+xwErm5ueKFF14Q9vb2AoCuDu0lwD/88INee+2l1w+u77///ls888wzwtHRUSiVSuHl5SUGDhwo4uLiKlWHEBUPJ6FVVFQk3NzcBADx22+/ldpG+5lqHyqVSnh4eIjevXuLpUuXllhnZdEu599//xUDBgwQNjY2wsHBQbz22mvizp07Jdr/+OOPonPnzsLa2lpYW1sLf39/ER0dLRISEnRtylrnWu3btxcAxKFDh3TTrly5IgAIT0/PUus8f/68iIqKEq6ursLc3Fw0bNhQ9O7dW6xfv16vXU5OjoiNjRW+vr7CwsJCODk5iY4dO4qZM2fqLsPXfsYzZswo8TqlfacetH//fhEdHS1atmwp7OzshLm5uWjUqJEYMWKEOH/+vF5b7WXppT26d++ua5eWliZiYmKEr6+vUCqVwt7eXkREROiGkLjfiRMnxFtvvSXatm0r6tevL8zMzISbm5t47rnnxLFjx8qtXYjiIQe025d69eqJyMhIcebMmRKXumu3BUeOHNGbv7ThBNRqtfjwww+Fm5ubsLS0FF26dBGnTp0q8/L5B+HecBLlKW84iYqsXbtWDB48WPj4+AhLS0uhUqlE8+bNxXvvvac3BEBZvLy8RK9evcTvv/8uWrduLZRKpfD39y+x/ShrnWlVZTv6119/iZCQEKFSqYSXl5eYN2+eXjuNRiOmTp0qvLy8hFKpFG3atBGbNm0qsb29///7rFmzhKenp1AqlSI0NFQ3XIzWww4nUdF3XojioZMAiAMHDpS6bkpz48YNMWzYMGFrayvs7OzEsGHDxN9//13hcBJxcXHi6aefFu7u7sLCwkK4u7uL559/vsRQMz///LNo3ry5MDMz01tmecPAlDWcxNq1a0VsbKxwdnYWlpaWolevXuLSpUsl5p81a5Zo2LChUCqVolOnTuKvv/4qsczyaittf5qTkyPeeOMN4e7uLszNzYWfn5+YMWOG3vAuQpT9Pbv/M87PzxdvvfWWCAgIEDY2NsLa2loEBASIBQsWlLo+yiK794JERERV5u3tjZYtW5Z503KqWP/+/XHy5MlS7wxCxqfW9PEiIiIyNdevX8fmzZt1d9Qg41drrmokIiIyFRcvXsT+/fvx9ddfw9zcvNxb05Fx4REvIiIiA9u9ezeGDRuGixcvYvny5RVe/UnGg328iIiIiAyER7yIiIiIDITBi4iIiMhATK5zvUajwbVr12BjY2Pwex8SERHRwxFCICcnB+7u7nq3DaxrTC54Xbt2DZ6enlKXQURERA8hOTkZHh4eUpfx0EwueGlvK5ScnAxbW1uJqyEiIqLKyM7OhqenZ7m3B6wLTC54aU8v2traMngRERHVMXW9m1DdPUlKREREVMcweBEREREZCIMXERERkYEweBEREREZCIMXERERkYEweBEREREZCIMXERERkYEweBEREREZCIMXERERkYEweBEREREZCIMXERERkYEweBEREREZiMndJLum5BepkZ6TL3UZZKQcrCxgreTXlYioruOWvJr8cy0bzyw4IHUZZKRU5nK81LkxXgn3ga3KXOpyiIjoITF4VRMZAKUZz9xS9RMA7hZqMH/Xeaw5dBnjuvlhyOONoDRTSF0aERFVkUwIIaQuwpCys7NhZ2eHrKws2NraSl0OUYWEENj+byqmbz2D8+l5AADP+paY+GQz9GntDrlcJnGFREQ1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jpg6dSoGDhyIw4cP46uvvsJXX30ldWkmqU+fPvj000/RqFEjtGjRAn///Tdmz56NF198UerSqIZwOIlqMm/ePMyYMQMpKSkIDAzE3LlzERwcLHVZJkkmk5U6fdmyZRgxYoRhi6FSdenShcNJSGjTpk2IjY1FYmIiGjdujJiYGIwePVrqskxSTk4O3n//fWzYsAFpaWlwd3fH888/j8mTJ8PCwkLq8qgGMHgRERERGQj7eBEREREZCIMXERERkYEweBEREREZCIMXERERkYEweBEREREZCIMXERERkYEweBEREREZCIMXERmct7d3lQdPHTFiBPr166f7u0uXLpgwYUK11lUTZDIZNm7cKHUZRFRLMHgRmaARI0ZAJpPpHo6OjujRowdOnDghdWmV9tNPP+Hjjz+WuowKXb9+HT179pS6DCKqJRi8iExUjx49cP36dVy/fh1xcXEwMzND7969pS6r0urXrw8bGxupy6iQq6srlEql1GUQUS3B4EVkopRKJVxdXeHq6orAwEBMmjQJycnJSE9P17U5efIkunXrBktLSzg6OuLll19Gbm6u7nnt6b+ZM2fCzc0Njo6OiI6ORmFhoa5NWloa+vTpA0tLSzRu3BirV6+usDa1Wo2YmBjY29vD0dERb7/9Nh68u9mDpxq9vb3xySefICoqCvXq1YOXlxd++eUXpKen4+mnn0a9evXQunVr/PXXX3rL2bdvH0JDQ2FpaQlPT0+8/vrryMvL01vu1KlT8eKLL8LGxgaNGjXSu6F0QUEBXnvtNbi5uUGlUsHLywvTpk3TPf/gqcbqWKdEVHcxeBERcnNzsWrVKvj6+sLR0REAkJeXh8jISDg4OODIkSP44YcfsGPHDrz22mt68+7atQvnz5/Hrl27sHz5cnz77bf49ttvdc+PGDECycnJ2LVrF9avX48FCxYgLS2t3HpmzZqFb7/9FkuXLsW+fftw8+ZNbNiwocL38cUXX6BTp074+++/0atXLwwbNgxRUVEYOnQojh07Bh8fH0RFRelC3Pnz59GjRw88++yzOHHiBNatW4d9+/aVeI+zZs1Cu3bt8Pfff2Ps2LEYM2YMEhISAABz587FL7/8gu+//x4JCQlYvXo1vL29S62vutYpEdVhgohMzvDhw4VCoRDW1tbC2tpaABBubm7i6NGjujZfffWVcHBwELm5ubppmzdvFnK5XKSkpOiW4+XlJYqKinRtnnvuOTFo0CAhhBAJCQkCgDh8+LDu+dOnTwsA4osvviizPjc3N/H555/r/i4sLBQeHh7i6aef1k0LDw8X48eP1/3t5eUlhg4dqvv7+vXrAoB4//33ddMOHjwoAIjr168LIYR46aWXxMsvv6z32nv37hVyuVzcuXOn1OVqNBrh7OwsFi5cKIQQYty4caJbt25Co9GU+l4AiA0bNgghqmedElHdxiNeRCaqa9euiI+PR3x8PA4fPozIyEj07NkTly5dAgCcPn0aAQEBsLa21s3TqVMnaDQa3dEeAGjRogUUCoXubzc3N90RrdOnT8PMzAxBQUG65/39/WFvb19mXVlZWbh+/TqCg4N108zMzNCuXbsK31Pr1q11/3ZxcQEAtGrVqsQ0bX3Hjx/Ht99+i3r16ukekZGR0Gg0uHjxYqnLlclkcHV11S1jxIgRiI+PR7NmzfD6669j27ZtZdZXHeuUiOo2M6kLICJpWFtbw9fXV/f3119/DTs7OyxZsgSffPJJpZdjbm6u97dMJoNGo6m2Oqvi/lpkMlmZ07T15ebm4pVXXsHrr79eYlmNGjUqdbna5WiX0bZtW1y8eBFbtmzBjh07MHDgQERERGD9+vXV8j4efD0iqtt4xIuIABTv3OVyOe7cuQMAeOyxx3D8+HG9jub79++HXC5Hs2bNKrVMf39/FBUV4ejRo7ppCQkJyMzMLHMeOzs7uLm54dChQ7ppDy6jurRt2xb//vsvfH19SzwsLCwqvRxbW1sMGjQIS5Yswbp16/Djjz/i5s2bJdpVxzolorqNwYvIROXn5yMlJQUpKSk4ffo0xo0bh9zcXPTp0wcAMGTIEKhUKgwfPhynTp3Crl27MG7cOAwbNkx3yq4izZo1Q48ePfDKK6/g0KFDOHr0KEaNGgVLS8ty5xs/fjw+++wzbNy4EWfOnMHYsWPLDWsP65133sGBAwfw2muvIT4+HomJifj5559LdHYvz+zZs7F27VqcOXMGZ8+exQ8//ABXV9dST6dWxzolorqNwYvIRG3duhVubm5wc3NDcHCw7iq7Ll26AACsrKzw+++/4+bNm2jfvj0GDBiA7t27Y968eVV6nWXLlsHd3R3h4eF45pln8PLLL8PZ2bnced58800MGzYMw4cPR0hICGxsbNC/f/+Hfatlat26NXbv3o2zZ88iNDQUbdq0weTJk+Hu7l7pZdjY2ODzzz9Hu3bt0L59eyQlJeG3336DXF5y81pd65SI6i6ZEA8MjkNERERENYJHvIiIiIgMhMGLiIiIyEAYvIiIiIgMhMGLiIiIyEAYvIiIiIgMhMGLiIiIyEAYvIiIiIgMhMGLiIiIyEAYvIiIiIgMhMGLiIiIyEAYvIiIiIgMhMGLiIiIyED+H2m2TPPYS6DzAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "TVD = []\n", "for chi in range(1,11):\n", " mps.set_cutoff(chi)\n", " mps.set_input_state(input_state)\n", " probs_mps = mps.prob_distribution()\n", " TVD.append(tvd(probs_mps, probs_slos))\n", "\n", "plt.plot(TVD)\n", "plt.xlabel('Bond dimension')\n", "plt.ylabel('TVD')\n", "plt.title('Evolution of the TVD between SLOS and MPS probability distributions');" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We can see that the TVD decreases as the size of the matrices increases, until reaching 0 for a bond dimension of 7." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## References\n", "\n", "> [1] Ulrich Schollwöck. The density-matrix renormalization group in the age of matrix product states. [Annals of Physics](https://doi.org/10.1016/j.aop.2010.09.012), 326(1):96–192, jan 2011.\n", "\n", "> [2] Changhun Oh, Kyungjoo Noh, Bill Fefferman, and Liang Jiang. Classical simulation of lossy boson sampling using matrix product operators. [Physical Review A](https://doi.org/10.1103/PhysRevA.104.022407), 104(2), aug 2021.\n", "\n", "> [3] Hui Wang, et al. Boson Sampling with 20 Input Photons and a 60-Mode Interferometer in a $10^{14}$-Dimensional Hilbert Space. [Physical Review Letters](https://link.aps.org/doi/10.1103/PhysRevLett.123.250503), 123(25):250503, December 2019. Publisher: American Physical Society." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: docs/source/notebooks/QLOQ_QUBO_tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# QLOQ (Qubit Logic on Qudits)\n", "\n", "Welcome to this tutorial, where we explore **QLOQ** (Qubit Logic on Qudits) for linear optical quantum computing. We will break down the core concepts behind encoding multiple qubits into a single photon (qudit), demonstrate how **intra-group** gates (like CNOT and single-qubit rotations) are implemented **without** the usual success-probability issues, and show how **Ralph CZ** gates can link multiple qudit blocks.\n", "\n", "## References\n", "\n", "[1] L. Lysaght, T. Goubault, P. Sinnott, S. Mansfield, P-E. Emeriau, \"Quantum circuit compression using qubit logic on qudits,\" https://arxiv.org/abs/2411.03878v1 (2024).\n", "\n", "## I. Introduction to QLOQ\n", "\n", "**QLOQ** (Qubit Logic on Qudits) is a specialized architecture in linear optics that encodes multiple qubits **within a single photon**. Traditionally, a 2-qubit operation in linear optics involves two separate photons interfering at a beam splitter with a probabilistic success rate. However, **QLOQ** circumvents that for intra-group gates by confining both qubits to one photon’s modes.\n", "\n", "- **Inter-group** entangling gates (between different photons) still rely on a *Ralph CZ* gate, which is post-selected (probabilistic). Inter-group entangling gates are accomplished via an unbalanced Ralph CZ to accomplish a multi-controlled Z operation. A balanced Ralph CZ has the same success probability(1/9) for each input state. An **unbalanced** version has different success probability for each input. It was chosen because it performs empirically better than the standard CCCZ and requires less post-selected modes.\n", "- **Intra-group** gates (like CNOT, CZ, single-qubit rotations) become deterministic mode permutations and transformations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## II. Qudits & CNOT in a Single Photon\n", "\n", "### Qudits\n", "A **qubit** is a 2-level system $|0\\rangle$ or $|1\\rangle$. A **qudit** extends this to $d$ levels. For a **2-qubit** block, $d = 4$. We treat each logical basis state as a unique optical mode:\n", "\n", "$|00\\rangle \\rightarrow$ mode 0 \n", "$|01\\rangle \\rightarrow$ mode 1 \n", "$|10\\rangle \\rightarrow$ mode 2 \n", "$|11\\rangle \\rightarrow$ mode 3 \n", "\n", "A single photon occupying exactly one of these four modes represents any superposition of the 2-qubit space.\n", "\n", "### CNOT Within a Qudit\n", "In standard linear optics, a **CNOT** between two separate photons is probabilistic. In QLOQ, if both qubits are in the *same* photon, a CNOT is merely a **mode permutation**:\n", "\n", "- $|10\\rangle \\rightarrow |11\\rangle$\n", "- $|00\\rangle$ and $|01\\rangle$ remain the same\n", "\n", "The complete mapping is:\n", "\n", "Mode Index | Binary State | CNOT Output\n", "-----------|--------------------|-------------\n", "0 | $\\lvert 00\\rangle$ | $\\lvert 00\\rangle$\n", "1 | $\\lvert 01\\rangle$ | $\\lvert 01\\rangle$\n", "2 | $\\lvert 10\\rangle$ | $\\lvert 11\\rangle$\n", "3 | $\\lvert 11\\rangle$ | $\\lvert 10\\rangle$\n", "\n", "This operation is **deterministic** because it's implemented entirely within the single photon's modes, bypassing the usual success probability constraints.\n", "\n", "> **CZ** is similarly done by a mode permutation + Hadamards on the target qubit." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## III. Applying Rotations in a Qudit Group\n", "When **multiple qubits** are encoded into a **single photon** (a qudit), each logical qubit corresponds to a specific **pairing of modes**. For a **2-qubit** group (4 modes total):\n", "\n", "- **Modes**: \n", " $0 \\rightarrow |00\\rangle$ \n", " $1 \\rightarrow |01\\rangle$ \n", " $2 \\rightarrow |10\\rangle$ \n", " $3 \\rightarrow |11\\rangle$\n", "\n", "- **Second qubit** flips between $|0\\rangle$ and $|1\\rangle$ in the *rightmost bit*, so to rotate it, we **pair**:\n", " - $(0,1) \\rightarrow |00\\rangle, |01\\rangle$\n", " - $(2,3) \\rightarrow |10\\rangle, |11\\rangle$\n", "\n", "- **First qubit** flips in the *leftmost bit*, so to rotate it, we **pair**:\n", " - $(0,2) \\rightarrow |00\\rangle, |10\\rangle$\n", " - $(1,3) \\rightarrow |01\\rangle, |11\\rangle$\n", "\n", "- So in practice we would apply a 2 mode parametrized beamsplitter for the specific rotation we want to achieve ($R_x$, $R_y$, $R_z$ etc) for each combination corresponding to the qubit we wish to act on.\n", "\n", "- Thankfully all this logic and all relevant swaps have been pre-coded into the ansatz builder so the user need not worry too much about them.\n", "\n", "> **Key Insight**: **Intra-group operations** are layerwise, meaning you stack them: first apply a rotation on the second qubit via parametrized beamsplitters on the correct pairs of modes, then do some internal mode swap, then apply a rotation on the first qubit by doing similarly, etc." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## IV. Building QLOQ Circuits with Perceval\n", "\n", "Perceval provides a `QLOQ ansatz` that helps you define:\n", "\n", "1. **Group Sizes**: e.g., `[Encoding.QUDIT2, Encoding.QUDIT2]`\n", " - can do Encoding.DUAL_RAIL, Encoding.QUDIT3 etc\n", "2. **Layers**: e.g., `[\"Y\"]` for Ry rotations\n", "3. **Phases**: the numerical angles for each rotation (or `None` for symbolic)\n", "4. **Entangling Gate** (`ctype`): either `\"cx\"` or `\"cz\"` inside each group\n", "\n", "Below is a minimal code snippet showing how to construct a QLOQ circuit (as a `Processor`) and display it." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from perceval import LogicalState, pdisplay, catalog, Encoding\n", "import perceval as pcvl\n", "import numpy as np\n", "from scipy.optimize import minimize\n", "from typing import List, Dict, Tuple, Optional\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of required phases: 16\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CPLX\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "CZ2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "CZ2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSEDCZ\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "CZ2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "CZ2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "[Group 0]\n", "\n", "[Group 1]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "\n", "[Group 0]\n", "\n", "[Group 1]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Example: Building a QLOQ circuit in Perceval\n", "# 1) Get the QLOQ ansatz from Perceval's catalog\n", "ansatz = catalog[\"qloq ansatz\"]\n", "\n", "# 2) Define groups of qubits (each group is a qudit)\n", "# For demonstration: one 2-qubit group (QUDIT2) + another 2 qubit group\n", "group_sizes = [Encoding.QUDIT2, Encoding.QUDIT2]\n", "\n", "# 3) Choose the single-qubit rotation layers we want (Y, X, or Z)\n", "layers = [\"Y\"] # e.g., apply RY rotations in each group\n", "#generally RY rotations are sufficient\n", "\n", "# 4) Compute how many parameter phases are needed\n", "nb_phases = ansatz.get_parameter_nb(group_sizes, len(layers))\n", "print(\"Number of required phases:\", nb_phases)\n", "\n", "# 5) (Optional) Provide numeric phases or use None for symbolic placeholders\n", "phases = None # Use symbolic parameters for visualization\n", "\n", "# 6) Build the QLOQ processor with 'ctype=\"cx\"'\n", "circ = ansatz.build_processor(\n", " group_sizes=group_sizes,\n", " layers=layers,\n", " phases=phases,\n", " ctype=\"cz\"\n", ")\n", "\n", "# 7) Display the resulting circuit\n", "pdisplay(circ, recursive=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ansatz in the qubit picture will then have this form, with layers of 2 qubit blocks linked by a multi-controlled Z gate which takes the form of an unbalanced CCCZ here using the ralph CZ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![qloqansatz.png](../_static/img/qloqansatz.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## V. Summary\n", "\n", "**QLOQ** offers a unique way to encode **multiple qubits** in **one photon** (creating qudits). The major advantage is that **CNOTs and single-qubit rotations** within that qudit can be executed deterministically without the usual success probability of two-photon gates. When linking multiple qudit blocks, **Ralph CZ** gates are used, which are **probabilistic** and post-selected.\n", "\n", "### Key Points\n", "- **Qudits**: 2 qubits = 4 modes in a single photon, 3 qubits = 8 modes, etc.\n", "- **Intra-group** gates (e.g., CNOT, Ry, Rz, Rx → Mode permutations and layers of beamsplitters.\n", "- **Inter-group** gates → Ralph CZ (post-selected) forming an unbalanced multi-controlled Z.\n", "- **Layerwise approach**: We apply rotations/cnot gates in a sequence of “layers,” possibly swapping modes to target the correct qubit.\n", "\n", "In upcoming sections, we will:\n", "- **Integrate** a classical optimizer (e.g., COBYLA) to do VQE-like or QAOA-like tasks on these QLOQ circuits.\n", "- Explore **QUBO** matrices and measure the resulting cost function from the photonic simulator." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## VI. Encoding Functions Explained\n", "\n", "These two functions, `to_fock_state` and `fock_to_qubit_state`, convert between:\n", "\n", "1. A **bitstring representation** of qubits (e.g., `\"0101\"`)\n", "2. A **Fock-state representation** (a list of occupation numbers, eventually wrapped in a `pcvl.BasicState`)\n", "\n", "### Key Idea\n", "- **Bitstring**: Represents qubits in the usual binary sense, e.g. `\"00\"` or `\"01\"`.\n", "- **Fock State**: In Perceval, a `BasicState` is a list of photon occupation numbers for each mode. A single photon occupying one of $2^n$ possible modes (for an $n$-qubit group) is stored as a one-hot vector (e.g., `[0,1,0,0]` for mode 1 out of 4).\n", "\n", "These functions handle situations where **multiple groups** of qubits are each encoded in a **qudit**. For example:\n", "\n", "- A group of **size=2** means 4 modes\n", "- A group of **size=3** means 8 modes, etc." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def fock_to_qubit_state(fock_state: List[int], group_sizes: List[int]) -> Optional[str]:\n", " \"\"\"\n", " Convert a Perceval Fock-state representation back to a multi-qubit bitstring.\n", "\n", " Args:\n", " fock_state: a one-hot vector for all groups (concatenated)\n", " group_sizes: each integer indicates how many qubits are in that group\n", "\n", " Returns:\n", " A bitstring (e.g. \"0101\"), or None if the Fock state is invalid.\n", " \"\"\"\n", "\n", " fock_state = [i for i in fock_state]\n", "\n", " # Expected total length = sum of (2^group_size) for each group\n", " expected_length = sum([2 ** size for size in group_sizes])\n", " if len(fock_state) != expected_length:\n", " return None\n", "\n", " offset = 0\n", " qubit_state_binary = \"\"\n", "\n", " for size in group_sizes:\n", " group_length = 2 ** size\n", " group_fock_state = fock_state[offset : offset + group_length]\n", "\n", " # We expect exactly one '1' in the chunk (indicating the photon mode)\n", " if group_fock_state.count(1) != 1:\n", " return None\n", "\n", " state_index = group_fock_state.index(1)\n", " # Convert index to binary (of width 'size' bits)\n", " binary_state = format(state_index, f'0{size}b')\n", " qubit_state_binary += binary_state\n", "\n", " offset += group_length\n", "\n", " return qubit_state_binary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How `fock_to_qubit_state` Works\n", "\n", "1. **Check** the total length needed: sum of $(2^{\\text{group\\_size}})$\n", "2. **Slice** each group's chunk out of the big `fock_state`\n", "3. Within that chunk, ensure exactly **one** entry is `1`\n", "4. The **index** of that `1` is the integer representation of the bits for that group\n", " - Convert that index to a binary string of width `n`\n", "5. Append all these binary substrings together, forming the **full** qubit bitstring\n", "\n", "#### Example \n", "- `fock_state = [1,0,0,0, 0,0,0,1]` (length=8)\n", "- `group_sizes = [2,2]`\n", "\n", "**Step by Step**:\n", "\n", "- Group A chunk: `[1,0,0,0]` → exactly one '1' at index=0 → binary of `0` with width=2 → `\"00\"`\n", "- Group B chunk: `[0,0,0,1]` → index=3 → binary= `\"11\"`\n", "\n", "Concatenate = `\"00\" + \"11\"` = `\"0011\"`\n", "\n", "### Edge Cases\n", "\n", "1. **Invalid Fock State**\n", " If a chunk has more than one `1` or none at all, `fock_to_qubit_state` returns `None`.\n", " This ensures we only accept states with exactly **one** photon per group.\n", " Thus, we have post-selected\n", "\n", "2. **Bitstring Offsets**\n", " The variable `offset` keeps track of how many bits we've already consumed from `qubit_state`.\n", " This ensures we map each portion of the bitstring to its corresponding qudit group.\n", "\n", "---\n", "\n", "By using the helper function:\n", "\n", "- `fock_to_qubit_state`: you can interpret the circuit's **measurement results** (a one-hot outcome) back into a **bitstring** for classical post-processing or optimization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## VII. QUBO Example with a Simple QLOQ ansatz Circuit (CVaR-VQE Approach)\n", "\n", "In this section of the notebook, we demonstrate how to tackle a **QUBO** (Quadratic Unconstrained Binary Optimization) problem using a **CVar-VQE** style approach in Perceval. This can also be verified with the other more photonic QUBO approach which uses the same QUBO matrix. We’ll show:\n", "\n", "1. **Building a simple QLOQ circuit** (e.g., with Ry layers) to produce a variational ansatz.\n", "2. **Sampling** from the circuit to get a distribution of bitstrings.\n", "3. **Computing CVaR** to measure the cost (objective function) and performing classical optimization (COBYLA).\n", "4. **Identifying the best bitstring** based on the final solution.\n", "5. **Optional**: Plotting the final probability distribution as a histogram for visual insight." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### What is CVaR-VQE?\n", "\n", "**CVaR-VQE** (Conditional Value-at-Risk Variational Quantum Eigensolver) is a hybrid quantum-classical optimization technique that **goes beyond** the usual average-cost minimization seen in standard VQE:\n", "\n", "1. **VQE Recap**\n", " - In a typical VQE, you prepare a **parametrized quantum circuit** (ansatz).\n", " - You **sample** from it to estimate the **average** energy (or cost).\n", " - A **classical optimizer** tunes the circuit parameters to **minimize** this average cost.\n", "\n", "2. **Why CVaR?**\n", " - In many problems, you don’t just care about the **average** cost. You also want to avoid **worst-case** outcomes.\n", " - **CVaR** (Conditional Value-at-Risk) focuses on the **worst $\\alpha$-fraction** of possible outcomes in your distribution.\n", " - Practically, we *sort* outcomes by cost and *average* the top $\\alpha$ portion (highest costs). If $\\alpha = 0.5$, that means we look at the top 50% of the distribution by cost. By **minimizing** that portion, we make sure the algorithm consistently avoids very high-cost states.\n", "\n", "3. **Combining CVaR with VQE**\n", " - We still build a **variational circuit** and measure its outputs, but instead of updating parameters to reduce the simple average cost, we **focus on the worst tail**.\n", " - This ensures the final circuit is **less likely** to produce very bad solutions.\n", "\n", "In short, **CVaR-VQE** aims to push the distribution of measured bitstrings toward reliably low-cost outcomes, rather than just optimizing the mean. This can be extremely useful for **QUBO** (Quadratic Unconstrained Binary Optimization) problems, where you want to avoid sampling high-cost bitstrings even occasionally.\n", "\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def compute_cvar(probabilities: List[float], values: List[float], alpha: float) -> float:\n", " \"\"\"\n", " Compute the Conditional Value at Risk (CVaR).\n", " Given a list of probabilities and corresponding values (costs),\n", " we take the worst alpha portion of outcomes and average them\n", " weighted by their probabilities.\n", " \"\"\"\n", " sorted_indices = np.argsort(values) # sort by ascending value\n", " probs = np.array(probabilities)[sorted_indices]\n", " vals = np.array(values)[sorted_indices]\n", " cvar = 0\n", " total_prob = 0\n", "\n", " for p, v in zip(probs, vals):\n", " if p >= alpha - total_prob:\n", " p = alpha - total_prob\n", " total_prob += p\n", " cvar += p * v\n", "\n", " return cvar / total_prob\n", "\n", "def expectation_value(vec_state: np.ndarray, qubo_matrix: np.ndarray) -> float:\n", " \"\"\"\n", " Compute the expectation value for a given state with respect to a QUBO matrix.\n", " Here, vec_state is a binary vector (e.g., [0,1,0,1]) converted to float,\n", " and qubo_matrix is the NxN matrix of the QUBO.\n", " \"\"\"\n", " return np.dot(vec_state.conjugate(), np.dot(qubo_matrix, vec_state))\n", "\n", "def extract_probability_distribution(job_results: Dict, group_sizes: List[int]) -> Tuple[Dict[str, float], int]:\n", " \"\"\"\n", " Extract probability distribution from sampling results.\n", " Returns:\n", " output_dict = {bitstring: probability}\n", " sum_valid_outputs = sum of all valid counts (used for normalization)\n", " \"\"\"\n", " output_dict = {}\n", " sum_valid_outputs = 0\n", "\n", " # First pass: count how many valid outputs (bitstrings)\n", " for res in job_results['results']:\n", " qb_state = fock_to_qubit_state(res, group_sizes)\n", " if qb_state:\n", " sum_valid_outputs += job_results['results'][res]\n", " output_dict[qb_state] = job_results['results'][res]\n", " \n", " divisor = sum_valid_outputs\n", " output_dict = {k: v/divisor for k, v in output_dict.items()}\n", " #compute probabilities by dividing by sum\n", "\n", " return output_dict" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def build_circuit(phases: List[float], group_sizes: List[int], layers: List[str]) -> pcvl.Circuit:\n", " \"\"\"\n", " Build the quantum circuit (QLOQ ansatz) with specified phases.\n", " \n", " \"\"\"\n", " ansatz = catalog[\"qloq ansatz\"]\n", " group_sizes_p = [Encoding.DUAL_RAIL if x == 1 else eval(f\"Encoding.QUDIT{x}\") for x in group_sizes]\n", " proc = ansatz.build_processor(\n", " group_sizes=group_sizes_p,\n", " layers=layers,\n", " phases=phases,\n", " ctype=\"cz\" #can be cx too\n", " )\n", " return proc" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def create_objective_function(qubo_matrix: np.ndarray,\n", " input_state: str,\n", " group_sizes: List[int],\n", " layers: List[str],\n", " sampling_size: int,\n", " alpha: float,\n", " verbose):\n", " \"\"\"\n", " Create the CVaR-VQE objective function for optimization.\n", " - qubo_matrix: the QUBO cost matrix\n", " - input_state: initial computational basis string (e.g. \"000000\")\n", " - group_sizes: list of integers, each representing # of qubits in that group\n", " - layers: e.g. [\"Y\"] or [\"Y\",\"X\"]\n", " - sampling_size: how many shots to gather per evaluation\n", " - alpha: the fraction for CVaR computation\n", "\n", " Returns:\n", " objective_function (callable): to be passed into an optimizer\n", " best_result (list reference): to track the best (lowest) loss + best bitstring\n", " \"\"\"\n", " best_result = [None] # store (loss, bitstring)\n", " iteration = [0] # track iteration count\n", "\n", " def objective_function(phases: np.ndarray) -> float:\n", " # Build circuit (processor)\n", " circ = build_circuit(phases.tolist(), group_sizes, layers)\n", " circ.with_input(LogicalState(input_state))\n", " \n", " # Sample from the circuit\n", " sampler = pcvl.algorithm.Sampler(circ, max_shots_per_call=sampling_size)\n", " job_results = sampler.sample_count(sampling_size)\n", "\n", " # Extract distribution\n", " output_dict = extract_probability_distribution(job_results, group_sizes)\n", " if not output_dict:\n", " return float('inf') # if no valid results, large penalty\n", "\n", " # Convert bitstrings to vectors => compute QUBO cost => gather CVaR\n", " probabilities = list(output_dict.values())\n", " values = [expectation_value(np.array(list(state)).astype(int), qubo_matrix)\n", " for state in output_dict.keys()]\n", "\n", " loss = compute_cvar(probabilities, values, alpha)\n", "\n", " # Track the best result\n", " bitstring = max(output_dict, key=output_dict.get)\n", " if best_result[0] is None or loss < best_result[0][0]:\n", " best_result[0] = (loss, bitstring)\n", "\n", " iteration[0] += 1\n", " if verbose == True:\n", " print(f\"Iteration {iteration[0]}: Loss = {loss}, Best bitstring = {bitstring}\")\n", "\n", " return loss\n", "\n", " return objective_function, best_result" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def optimize_qubo(qubo_matrix: np.ndarray,\n", " input_state: str,\n", " group_sizes: List[int],\n", " layers: List[str],\n", " sampling_size: int = 100000,\n", " alpha: float = 0.5,\n", " maxiter: int = 50,\n", " verbose=False) -> Tuple[float, str, np.ndarray]:\n", " \"\"\"\n", " Run the optimization using COBYLA to find phases that minimize CVaR for the given QUBO problem.\n", " - qubo_matrix: your QUBO cost matrix\n", " - input_state: initial bitstring\n", " - group_sizes: e.g. [2,2,2]\n", " - layers: e.g. [\"Y\"]\n", " - sampling_size: how many shots per circuit evaluation\n", " - alpha: fraction for CVaR\n", " - maxiter: max COBYLA iterations\n", "\n", " Returns:\n", " final_loss (float): best CVaR found\n", " best_bitstring (str): best measured bitstring\n", " optimal_phases (np.ndarray): the phase vector that achieved the best result\n", " \"\"\"\n", " ansatz = catalog[\"qloq ansatz\"]\n", " \n", " group_sizes_p = [Encoding.DUAL_RAIL if x == 1 else eval(f\"Encoding.QUDIT{x}\") for x in group_sizes]\n", " nb_phases = ansatz.get_parameter_nb(group_sizes_p, len(layers))\n", "\n", " # Random initial guess for the phases\n", " initial_phases = np.random.uniform(0, 2*np.pi, nb_phases)\n", "\n", " # Build the objective function\n", " objective_function, best_result = create_objective_function(\n", " qubo_matrix, input_state, group_sizes, layers, sampling_size, alpha, verbose=verbose)\n", "\n", " # Minimize\n", " result = minimize(\n", " objective_function,\n", " initial_phases,\n", " method='cobyla',\n", " options={\n", " 'maxiter': maxiter,\n", " }\n", " )\n", "\n", " best_loss = result.fun\n", " best_bitstring = best_result[0][1]\n", " return best_loss, best_bitstring, result.x" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def plot_bitstring_distribution(prob_dict: Dict[str, float], top_n: int = 10, title: str = \"Top Bitstring Probabilities\"):\n", " \"\"\"\n", " Plot a histogram of the top N most probable bitstrings.\n", "\n", " Args:\n", " prob_dict: e.g. {\"000000\": 0.25, \"100100\": 0.15, ...}\n", " top_n: how many top states to display\n", " title: plot title\n", " \"\"\"\n", " # Convert dict to list of (bitstring, probability) pairs\n", " items = list(prob_dict.items())\n", "\n", " # Sort descending by probability\n", " items.sort(key=lambda x: x[1], reverse=True)\n", "\n", " # Take the top 'top_n' states\n", " items = items[:top_n]\n", "\n", " # Unzip into two lists\n", " bitstrings, probs = zip(*items)\n", "\n", " plt.figure(figsize=(8, 4))\n", " plt.bar(bitstrings, probs, color='darkviolet')\n", " plt.ylabel(\"Probability\")\n", " plt.xlabel(\"Bitstring\")\n", " plt.title(title)\n", " plt.xticks(rotation=45, ha='right')\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## VIII. Putting It All Together\n", "\n", "Below is a complete **example** usage, showing how to:\n", "\n", "1. Define a sample QUBO matrix (6-qubit problem).\n", "2. Optimize using Ry layers in a QLOQ circuit.\n", "3. Print the final result and best bitstring.\n", "4. Optionally, sample the final circuit once more to plot the output distribution." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "=== Final Optimization Results ===\n", "Final CVaR Loss: -27.1173748\n", "Best Bitstring: 010001\n", "Optimal Phases: [1.79428709 6.42920804 6.50974881 1.33076923 6.54604002 4.42032268\n", " 2.76595847 1.05846287 6.5138662 4.25633883 4.4176755 4.25184957\n", " 1.33692428 3.82872455 5.19343458 3.11518911 5.0788073 3.33595093\n", " 3.58811962 7.09218881 2.64311878 4.98881303 4.4733493 4.57674368]\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAArwAAAGqCAYAAADp3ZdYAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAS/1JREFUeJzt3Qm8TfX+//HPMc9DZEyGEDJGpEiDollJuIroaqKU6KZbKHVJKiWl2zwpDbd5UCmNUoYSRZFCmWWeWf/H+/t7rP3fezvnOI7j7LPWej0fj83Za++zz/rutfZan/1dn+/nm+Z5nmcAAABASOVL9QoAAAAAhxIBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQaAS8QcL///rulpaXZ008/fUj/To0aNeyyyy4L7PqL/ob+lv5mfLvOOeccyw1Tp051f1//52WbN2+2f/7zn1apUiW3vtdff71FyfDhw127c5I+O9rXcluq/i6Q1xDwAnmcH6Sld7v55pstr4lfvwIFCthhhx1mzZs3twEDBthPP/2UY3/n4YcfzpUgOWzrJi1btnTb55FHHkn38f/85z9u/a+++mp77rnn7NJLL7Wvv/7aBYLr16/P9fX96quv7IILLrCKFSta4cKFXQB35ZVX2pIlS7L9mlu3bnXtyetfPrLir7/+cm35/vvvU70qQJ6V5nmel+qVAJAxBR69e/e2O+64w2rWrJnwWMOGDa1Jkya2Y8cOK1iwoOXPn/+QrYeCjJNPPnm/gZwCqdNPP9169uxpOrxs2LDBfvjhB3vllVdsy5Ytdvfdd9vAgQNjz9dzsrP+anv58uUPKGDZs2eP7dq1ywVNfg+e2qXXeuedd7L8Otldt71799rOnTutUKFCli9favobfv31V6tbt65rd9WqVe3LL7/c5znHH3+8+7IS/9iYMWNs8ODBtnjx4lztMRw3bpz7slSrVi3XW1m5cmX7+eef7fHHH3ePv/fee3bCCScc8OuuWbPGDj/8cBs2bJgLFuPt3r3b3YoUKZJj7dB+p+2vfS+nzZgxw4477jh76qmn9rkKcyj/LhAkBVK9AgCy5swzz7QWLVqk+1hOnphzggKqSy65JGHZqFGj7Nxzz7Ubb7zR6tWrZ2eddZZbrsDzUK+/Au3ixYu7gPpQfinYHwW5qd5Wzz//vFWoUMHuvfdeu+iii1x6R3IAu2rVKmvQoEGurI96WosVK5Zhz67SKdq0aWMffPBBwvPU+3ziiSe6NsybN8/Kli2bY+ukYF+3nKQvdKmQqr8L5Dnq4QWQdz311FO6CuN999136T6+ePFi97ie5+vVq5dXvHhxb9myZd7555/vfi5fvrx34403ert37074/Xvuucdr3bq1d9hhh3lFihTxjj32WO+VV17Z5+9Ur17dve7+aF369euX7mN//PGHV6BAAe+EE07IdP2XL1/uXXbZZV7VqlW9QoUKeZUqVfLOO+8891x/XfQ78bd27dolvF9Tp071rr76au/www/3ypQpk/CY/zr+a5199tne5MmTvSZNmniFCxf26tev77322msJ6z5s2DD3u8mSXzOzdfv000/dff0f7+WXX3bvu97/cuXKeT169HDbLt6BbNPM1K5d27vmmmu8HTt2uPflrrvuij3mr1/yTX87veXx7+Nzzz0Xa0PZsmW9rl27ekuWLEn423ofjjnmGG/GjBle27ZtvaJFi3oDBgzIcF07dOjg5c+f3/vtt9/SffyZZ55x6zFy5Mh93qdFixZ5Z5xxhlesWDGvcuXK3u233+7t3bs3YZ9LvmkbZ7St/f1a20r7h9p5/PHHe3PmzHGPT5gwwTvqqKPc/qN2xr83/npp34h/L9Jbh/jPwtq1a932bdiwoWtTyZIlvY4dO3rff//9freZ/xrJf1c2b97sDRw40DviiCPc56tu3bruOOC/P8ltfv31191203MbNGjgvf/++xluMyCvoocXCAilBugybDxdNs/s8n2HDh2sVatW7nL0xx9/7Hr1jjrqKNc75nvggQfsvPPOsx49erjL7S+99JJ16dLFXeI/++yzc7QNRx55pLVr184+/fRT27hxo5UqVSrd53Xu3Nn12l177bWu91E9jh999JHL2dT9sWPHusdKlChh//73v93vKL8z3jXXXOMuWQ8dOtT18O7vMn/Xrl3tqquusl69erlLw3oP1Kuo9IwDkZV1Sy9lRZekR44caStXrnTbRL2bs2fPtjJlyhzwNs3I9OnTbeHCha59Squ48MIL7YUXXrBbbrnFPV6/fn2Xs3vDDTfYEUcc4XrjpVGjRm7fePHFF+3++++P7Xd6f+Wuu+6y2267zS6++GI32G316tUuFeGkk07apw1r1651Vyu6devmrgJk9N6o53fKlCnWtm3bfVJ5fNpmV1xxhdtX4/PZ9T517NjRpWaMHj3abUelLihNQalBWm/lL+s9U26w3gdp3Lhxpu/fF198YW+99Zb169fP3df20oDHm266yeVta5/7+++/3d/s06ePffLJJxm+lvYNvVfJve+TJ092PfDy22+/2RtvvOH2Rb0H2jceffRR9xlSPnyVKlXcNlObtJ/rvdD7JRmleSiO1eddn8HLL7/cmjZt6v6m0lX+/PNPt33jKa3lf//7n2tbyZIl7cEHH3SfT30Wy5Url+n7BeQpqY64AWTO70FM75ZZD6+W3XHHHQmv1axZM6958+YJy7Zu3Zpwf+fOna5H6dRTT83xHl5Rj56e88MPP6S7/n///be7rx6nzKjHye85Te/9atOmzT49nxn18GpZfI/uhg0bXK+g3q8D7eHNbN2Se3j1XleoUMG939u2bYs975133nHPGzp0aLa2aUb69+/vVatWLdaT9+GHH7rXnD17dsLz/F7veNoeye2U33//3fXCxvcUy48//uh68+OX+72a6g3dH/Vi6rmZ9QBL48aN3dWJ5Pfp2muvjS1Te9Ue9VCuXr3aLdP/8b268TLq4VXvbXz7H330UbdcVyA2btwYWz5kyJB93qv0elrjffXVV17BggW9Pn36xJZt377d27NnT8Lz9Jpaj/j9QFd/ko8BGf3dN954wz33zjvvTHjeRRdd5KWlpXkLFy5MaLPes/hl+txq+bhx4zJsC5AXUaUBCIjx48e7Xs742/6oxzKeen/UaxSvaNGisZ/VO6WeZD1v1qxZdiio51M2bdqU7uNaH/U+asCX1ie7+vbtm+V8XfWUqafPp55nDbpT7+SKFSvsUNFgI/Veq/csPrdXPevKc3733XeztU3To97NSZMmuV5Rf8Deqaee6noT1cubXer906Ao9e7qCoR/U0mzOnXquJ7EeBo8pR7t/fH3D/UqZkaP62pBsv79+8d+Vnt1X73U6hXPrtNOOy0h31k97aIez/j19JdnZbuI9jHlIqu3VT3F8e+VP7hRvdbqHdfn5+ijj87251OD/PS5uO666xKWqzdfMe7777+fsLx9+/buCoJPveD6fGS1bUBeQUoDEBAqJZXRoLX0KIDyLzn7NLAnOYjU5eA777zTlTRStQRfTtchja/xmlkgo5O8KjnoBKzL3bosrcvGCkAVRGVVRpfB01O7du192quBd6JBXQfydw/EH3/84f5XAJNMAW9yBYWsbtP0fPjhhy7VQPuR0hp8p5xyiktV0HuencoRSgdRoKTgNiuDplQZQl9o9sffPzL6YuTT48n7ktqhqg4Zbc+DScmJV7p0afd/tWrV0l2ele2iLyL6sqCAVl8e4qsp6IuE0lsUBKs6hp7jy246gfY5fcFLfs+UGuE/nlmbD2SfA/ISAl4gpLLSu6mcROXzKddSJ1WVfFKAohzPiRMnHpL1mjt3rlu3zAJSjcxXRQflLyq/UPmhypdUTmSzZs2y9Hfie65zQkZfAOKDkEPtYCpM+L24Cq7S89lnn7ng90ApKNN7o57B9NbP79E/0O2iLyGqlDBnzpwMn6MvaAsWLDigL4KH4v3PaHlWqn4qd3batGmu51l508n1kLXvKx94xIgRrqa1gnl9PvS+54aDaRuQlxDwAhH22muvuV5DBZXxPUsKeA8FDXRRYNW6dev9XqrWZVT18uqmXkRd7tUALQ3syekeaPV46gQe/5q//PKL+9+/hO2XvdLEC/GDsJJ7xA5k3apXr+7+V9Cm9IJ4WuY/frA0aO/NN9906Qy6dJ5Ml7cVEGcW8GbUJm0nvXf6AuP3ouYElZHT+uhLjt7j9N6Ll19+2QW9yTPlKRjUJff49UnenofqCsaB0ABRDXLUTQPRkr366qvuPXjiiScSlmsfjB+weiBt0fuo4Dq5Z3z+/Pmxx4EwIocXiDD13uhkGd9LqUu+6lnNaevWrbPu3bu7v+VXL8hodP727dv3Cap0co5PuVBAlFOzfmmmqtdffz12Xzmhzz77rAuy/XQGP4/x888/Twgkn3nmmX1eL6vrpp5J5dBOmDAhoW3qLdXkCjlVJUNt07qquoAC3uSbAkZ9+Ylfh/TaJMntUoUD7Ue33377Pr1+uq+80+y69dZb3WtoMoVt27YlPKZL/KqOoKsSmnUt2UMPPZSwHrqvqxfKwxW/pm8qZo7zr3SoSoMqVWhijfTofU1+TzWBi6opZGXbpEf1r/UZjH9/RNUZdCxQBQ0gjOjhBSJMAdV9993nSjj94x//cAOoNDhOl5Mzu5S8P+pNU0+sTtYKHv2Z1pS/6/+9zH5XQYkuvWvyA13WVsCmkkwqZeXTdMUqLaX8Y62vAsfkXtKsUk+gSjR99913Lm/4ySefdH8vvqf7jDPOcPmMep4uQysY0fOUU5s8xW1W100BmHJnNYhLPXz6QuCXJVNPpMqD5QT13irnM6NSVUpreeyxx9wgOb9EVzK1SfRlRdtB6660E30RUDuHDBnivix16tTJfTlRQKrtplJZgwYNytZ6K9VG5dc0M58GS/kzrak3UuurnlwNwkqedEJXLVSKTCXmNIBMXyDUNpVf83OglVqh/UsD+bT9lS6gGfJ0yw3+wD210b9q4dN2Ug6yvoio5Jieq2U//vij25bJ+cnaBrrqoC9Oeu8VAKvd6aUNaZup11jbUdtLMzUqv1tXAJQqET9ADQiVVJeJAHDoJp7ISrmlJ554wqtTp44rdVSvXj33Ouk970DKkvm3fPnyuckNVDpL5aXmzZu33/Vfs2aNK2umdVEbSpcu7bVq1coV/I+3YsUKV2pKxfjTm3givfdrfxNPqMSV/z6kN/nGzJkz3bqoVNORRx7p3Xfffem+ZkbrltHEE5MmTXLvkf62SmxlNvFEsozKpflWrlzpyoNdeumlGT5Hpek0QcMFF1yQ8J4kGzFihJsMRNs1uc0q66ZScFpH3fQeajsuWLBgn4knDtTnn3/uJtvQRBsq3aX3vm/fvq4kWrL0Jp6oWLGie5+SS3x9/fXXrqSbtmdWJ55Ib99NLqHnb+f4fSi5PFh6E5QkTxqhsmSaeEIl8jRJx4knnuhNmzbNvY/JZe/efPNNNymEtvX+Jp7YtGmTd8MNN3hVqlRx76c+/5lNPJEsq8cCIC9J0z+pDroBAMgJ6gVW7qtfDQQAhBxeAAAAhBoBLwAAAEKNgBcAAAChRg4vAAAAQo0eXgAAAIQadXjTodqOKkSveoZ5YTYeAAAAJFKSgmYNrFKlipt2OzMEvOlQsFutWrVUrwYAAAD2Y+nSpXbEEUdk+hwC3nT484vrDSxVqlSqVwcAAABJNJOnOij9uC0zBLzp8NMYFOwS8AIAAORdWUk/ZdAaAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqBHwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgVSPUK4P+MT5trQdTPa5jqVQAAAMgUPbwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQaAS8AAABCjYAXAAAAoUbACwAAgFAj4AUAAECoEfACAAAg1Ah4AQAAEGoEvAAAAAg1Al4AAACEGgEvAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqBHwAgAAINTyRMA7fvx4q1GjhhUpUsRatWpl3377bYbPfeyxx6xt27ZWtmxZd2vfvv0+z/c8z4YOHWqVK1e2okWLuuf8+uuvudASAAAA5DUpD3gnTZpkAwcOtGHDhtmsWbOsSZMm1qFDB1u1alW6z586dap1797dPv30U5s2bZpVq1bNzjjjDPvzzz9jzxk9erQ9+OCDNmHCBJs+fboVL17cveb27dtzsWUAAADIC9I8dYemkHp0jzvuOHvooYfc/b1797og9tprr7Wbb755v7+/Z88e19Or3+/Zs6fr3a1SpYrdeOONNmjQIPecDRs2WMWKFe3pp5+2bt267fMaO3bscDffxo0b3Tro90qVKmW5YXzaXAuifl7DVK8CAACIoI0bN1rp0qWzFK+ltId3586dNnPmTJdyEFuhfPncffXeZsXWrVtt165ddthhh7n7ixcvthUrViS8pt4MBdYZvebIkSPdc/ybgl0AAACEQ0oD3jVr1rgeWvW+xtN9Ba1Z8a9//cv16PoBrv97B/KaQ4YMcd8O/NvSpUuz2SIAAADkNQUswEaNGmUvvfSSy+vVgLfsKly4sLsBAAAgfFLaw1u+fHnLnz+/rVy5MmG57leqVCnT3x0zZowLeD/88ENr3LhxbLn/e9l5TQAAAIRPSgPeQoUKWfPmzW3KlCmxZRq0pvutW7fO8PdUhWHEiBH2wQcfWIsWLRIeq1mzpgts419TSc2q1pDZawIAACCcUp7SoJJkvXr1coFry5YtbezYsbZlyxbr3bu3e1yVF6pWreoGlsndd9/tauxOnDjR1e7183JLlCjhbmlpaXb99dfbnXfeaXXq1HEB8G233ebyfDt16pTStgIAACCCAW/Xrl1t9erVLohV8Nq0aVPXc+sPOluyZImr3OB75JFHXHWHiy66KOF1VMd3+PDh7uebbrrJBc1XXHGFrV+/3tq0aeNe82DyfAEAABBMKa/DG/S6bjmFOrwAAAAhrMMLAAAAHGoEvAAAAAg1Al4AAACEGgEvAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqBHwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQaAS8AAABCjYAXAAAAoUbACwAAgFAj4AUAAECoEfACAAAg1Ah4AQAAEGoEvAAAAAg1Al4AAACEGgEvAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqBHwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQaAS8AAABCjYAXAAAAoUbACwAAgFAj4AUAAECoEfACAAAg1Ah4AQAAEGoEvAAAAAg1Al4AAACEGgEvAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqKU84B0/frzVqFHDihQpYq1atbJvv/02w+fOmzfPOnfu7J6flpZmY8eO3ec5w4cPd4/F3+rVq3eIWwEAAIC8KqUB76RJk2zgwIE2bNgwmzVrljVp0sQ6dOhgq1atSvf5W7dutVq1atmoUaOsUqVKGb7uMcccY8uXL4/dvvzyy0PYCgAAAORlKQ1477vvPuvbt6/17t3bGjRoYBMmTLBixYrZk08+me7zjzvuOLvnnnusW7duVrhw4Qxft0CBAi4g9m/ly5c/hK0AAABAXpaygHfnzp02c+ZMa9++/f9fmXz53P1p06Yd1Gv/+uuvVqVKFdcb3KNHD1uyZEmmz9+xY4dt3Lgx4QYAAIBwSFnAu2bNGtuzZ49VrFgxYbnur1ixItuvqzzgp59+2j744AN75JFHbPHixda2bVvbtGlThr8zcuRIK126dOxWrVq1bP99AAAA5C0pH7SW084880zr0qWLNW7c2OUDv/fee7Z+/Xp7+eWXM/ydIUOG2IYNG2K3pUuX5uo6AwAA4NApYCmivNr8+fPbypUrE5brfmYD0g5UmTJlrG7durZw4cIMn6N84MxyggEAABBcKevhLVSokDVv3tymTJkSW7Z37153v3Xr1jn2dzZv3myLFi2yypUr59hrAgAAIDhS1sMrKknWq1cva9GihbVs2dLV1d2yZYur2iA9e/a0qlWruhxbf6DbTz/9FPv5zz//tO+//95KlChhtWvXdssHDRpk5557rlWvXt3++usvV/JMPcndu3dPYUsBAAAQyYC3a9eutnr1ahs6dKgbqNa0aVM32MwfyKbqCqrc4FMA26xZs9j9MWPGuFu7du1s6tSpbtmyZctccLt27Vo7/PDDrU2bNvbNN9+4nwEAABA9aZ7nealeibxGZclUrUED2EqVKpUrf3N82lwLon5ew1SvAgAAiKCNBxCvha5KAwAAABCPgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUshXwfvrppzm/JgAAAEBeCXg7duxoRx11lN155522dOnSnF8rAAAAIJUBr2Y469+/v7366qtWq1Yt69Chg7388stu9jMAAAAg8AFv+fLl7YYbbnDT+k6fPt3q1q1r11xzjVWpUsWuu+46++GHH3J+TQEAAIBUDFo79thjbciQIa7Hd/Pmzfbkk09a8+bNrW3btjZv3ryDfXkAAAAgNQHvrl27XErDWWedZdWrV7fJkyfbQw89ZCtXrrSFCxe6ZV26dDm4tQMAAAAOUoHs/NK1115rL774onmeZ5deeqmNHj3aGjZsGHu8ePHiNmbMGJfiAAAAAAQu4P3pp59s3LhxduGFF1rhwoUzzPOlfBkAAAACmdIwbNgwl66QHOzu3r3bPv/8c/dzgQIFrF27djmzlgAAAEBuBrynnHKKrVu3bp/lGzZscI8BAAAAgQ54lbublpa2z/K1a9e6/F0AAAAgkDm8ytkVBbuXXXZZQkrDnj17bM6cOXbCCSfk/FoCAAAAuRHwli5dOtbDW7JkSStatGjssUKFCtnxxx9vffv2ze66AAAAAKkNeJ966in3f40aNWzQoEGkLwAAACCcZclUpQEAAAAIVcCrKYSnTJliZcuWtWbNmqU7aM03a9asnFo/AAAAIHcC3vPPPz82SK1Tp04H91cBAACAXJLmaQQaEmzcuNEN0FNd4VKlSuXK3xyfNteCqJ/3/6eUBgAAyIvxWrbq8AIAAAChS2lQ7m5mebvx0puFDQAAAMjTAe/YsWMP7ZoAAAAAqQx4e/XqdSj+PgAAAJA3Al4lBvsJwfo5M7k10AsAAADI0Rze5cuXW4UKFaxMmTLp5vOq4IOW79mzJ6svCwAAAOSNgPeTTz6xww47zP386aefHsp1AgAAAHI/4G3Xrl26PwMAAAChCHiT/f333/bEE0/Yzz//7O43aNDAevfuHesFBgAAAPKCbE088fnnn1uNGjXswQcfdIGvbvq5Zs2a7jEAAAAg0D28/fr1s65du9ojjzxi+fPnd8s0UO2aa65xj/344485vZ4AAABA7vXwLly40G688cZYsCv6eeDAge4xAAAAINAB77HHHhvL3Y2nZU2aNMmJ9QIAAAByN6Vhzpw5sZ+vu+46GzBggOvNPf74492yb775xsaPH2+jRo3KmTUDAAAAckCap9kisiBfvnxuUon9PT0ME09oJrnSpUvbhg0bcm3WuPFpcy2I+nkNU70KAAAggjYeQLyW5R7exYsX58S6AQAAALkqywFv9erVD+2aAAAAAHlp4gn56aefbMmSJbZz586E5eedd97BrhcAAACQuoD3t99+swsuuMDV243P69XPEvQcXgAAAES8LJkqNGhWtVWrVlmxYsVs3rx5boa1Fi1a2NSpU3N+LQEAAIDc7OGdNm2affLJJ1a+fHlXvUG3Nm3a2MiRI13JstmzZ2d3fQAAAIDU9/AqZaFkyZLuZwW9f/31V2xg24IFC3J2DQEAAIDc7uFt2LCh/fDDDy6toVWrVjZ69GgrVKiQ/fe//7VatWodzPoAAAAAqQ94b731VtuyZYv7+Y477rBzzjnH2rZta+XKlbNJkybl7BoCAAAAuR3wdujQIfZz7dq1bf78+bZu3TorW7ZsrFIDAAAAEPg6vLJ06VL3f7Vq1XJifQAAAIDUD1rbvXu33XbbbW7+4ho1aribflaqw65du3J2DQEAAIDc7uG99tpr7X//+58brNa6detYqbLhw4fb2rVr7ZFHHjmYdQIAAABSG/BOnDjRXnrpJTvzzDNjyxo3buzSGrp3707ACwAAgGCnNBQuXNilMSRTmTKVJwMAAAACHfD279/fRowYYTt27Igt08933XWXewwAAAAIXErDhRdemHD/448/tiOOOMKaNGni7msiip07d9ppp52W82sJAAAAHOqAV1UY4nXu3DnhPmXJAAAAEOiA96mnnjokKzB+/Hi75557bMWKFa63eNy4cdayZct0nztv3jwbOnSozZw50/744w+7//777frrrz+o1wQAAEC4ZSuH17d69Wr78ssv3U0/HyhNQzxw4EAbNmyYzZo1ywWnmsVt1apV6T5/69atVqtWLRs1apRVqlQpR14TAAAA4ZatgHfLli3Wp08fq1y5sp100knuVqVKFbv88stdUJpV9913n/Xt29d69+5tDRo0sAkTJlixYsXsySefTPf5xx13nOu57datm6sUkROv6Q+427hxY8INAAAAEQ541YP62Wef2dtvv23r1693tzfffNMtu/HGG7P0GhrgptSE9u3b//+VyZfP3dckFtmR3dccOXKky1H2b+QjAwAARDzgfe211+yJJ55wE0+UKlXK3c466yx77LHH7NVXX83Sa6xZs8b27NljFStWTFiu+8q9zY7svuaQIUNsw4YNsdvSpUuz9fcBAAAQkpnWlLaQHFRKhQoVDiilIa9QekRGKRIAAACIYA9v69at3aCw7du3x5Zt27bNbr/9dvdYVpQvX97y589vK1euTFiu+xkNSEvFawIAACCCAe/YsWPtq6++chNPaKIJ3ZT3+vXXX9sDDzyQpdfQFMTNmze3KVOmxJbt3bvX3c9q0JwbrwkAAIAIpjQ0atTIfv31V3vhhRds/vz5bln37t2tR48eVrRo0QMa/NarVy9r0aKFq5OrQFoVIFRhQXr27GlVq1Z1g8r8QWk//fRT7Oc///zTvv/+eytRooTVrl07S68JAACAaDnggHfXrl1Wr149e+edd1z5r4PRtWtXV79Xk0loUFnTpk3tgw8+iOUHL1myxFVZ8P3111/WrFmz2P0xY8a4W7t27Wzq1KlZek0AAABES5rned6B/pJ6XT/++GOrX7++hZHq8Ko8mSo2qAJFbhifNteCqJ/XMNWrAAAAImjjAcRr2crh7devn9199922e/fu7K4jAAAAkHdzeL/77js3EOzDDz90+bzFixdPePx///tfTq0fAAAAkPsBb5kyZaxz584H95cBAACAvBbwqsTXPffcY7/88ourknDqqafa8OHDD6gyAwAAAJCbDiiH96677rJbbrnFlQHTwLUHH3zQ5fMCAAAAoQh4n332WXv44Ydt8uTJ9sYbb9jbb7/tavGq5xcAAAAIfMCrurhnnXVW7H779u0tLS3N1ccFAAAAAh/wqgxZkSJFEpYVLFjQTUYBAAAABH7QmuaouOyyy6xw4cKxZdu3b7errroqoTQZZckAAAAQyIC3V69e+yy75JJLcnJ9AAAAgNQFvE899VTO/nUAAADgEMvW1MIAAABAUBDwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhNoBVWkADtb4tLkWRP28hqleBQAAkE308AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQag9aAHMbAPAAA8hZ6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YcPc73Djxo0bN27cuHGzQN2WLl2635gzpT28a9assT179rje13i6P3/+/HR/Z8WKFek+X8v9x/1lGT0n2ZAhQ1xahW/v3r22bt06K1eunKWlpVmQ6dtPtWrVbOnSpVaqVCkLqyi0MwptjEo7o9DGqLQzCm2MSjuj0MawtVM9u5s2bbIqVark/ZSGvKBw4cLuFq9MmTIWJtqpg75jZ0UU2hmFNkalnVFoY1TaGYU2RqWdUWhjmNpZunTpvD9orXz58pY/f35buXJlwnLdr1SpUrq/o+WZPd///0BeEwAAAOGV0oC3UKFC1rx5c5syZUpCOoHut27dOt3f0fL454sGrfnPr1mzpgts45+j7ntVa8joNQEAABBeKU9pUO5sr169rEWLFtayZUtXYWHLli2xqg09e/a0qlWr2siRI939AQMGWLt27ezee++1s88+21566SWbMWOG/fe//3WPK+f2+uuvtzvvvNPq1KnjAuDbbrvN5XeofFnUKFVDJd+SUzbCJgrtjEIbo9LOKLQxKu2MQhuj0s4otDFK7UyWppFrqV4JlRS755573KCypk2buhq6moBCTj75ZDcpxdNPP50w8YTKjv3+++8uqFXd3bPOOiv2uJqkjakgeP369damTRt7+OGHrW7duilpHwAAACIe8AIAAAChnWkNAAAAOJQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChlvKJJ3DgVElOE2wg+FavXm3r1q2znTt32jHHHGP58vEdNKh27dplBQsWtDD7+++/3b66bds2Vx89rJYvX+7qvKutDRo0sMMPPzx0x94o7K9APM6uAfPzzz/bXXfdZZs3b7Yw0wk17ObMmWPHH3+8de7c2Zo0aWIXXXSRPffccxY2mtpbtzCbP3++DRo0yH0+w7y/tm3b1k477TQ7+uij7corr7RPPvnEwubHH390s35effXVdsopp1iXLl1iM30q2A1D6foo7K/y22+/uUmrNPPqDz/84DoYJAzbMGrH2ByhiScQDL/++qt3+OGHe2lpaV6/fv28bdu2eWE0d+5cr3Hjxt7bb7/thdWKFSu86tWrezfeeKP3888/e1OnTvU6derkHXvssd6IESO8sJg3b55Xr14974EHHvA2b97shdHChQu9qlWrus/lhRde6O6HzbJly7zKlSu7/fXzzz/3Xn31Va9Zs2beqaee6j355JNeWKxdu9arW7eud8MNN3jLly/3Zs6c6X6uWbOmd9VVV8Wet3fvXi+oorC/ypw5c7xy5cp5J5xwgnfkkUd6VapU8f7xj394s2bNCvw2jNoxNqcQ8AaEdmQdcLt27eo9//zzXtGiRb0rrrgidEHvH3/84TVo0MArXbq0d9hhh3nvvvuuF0ZfffWVV79+fe+vv/6KLVu0aJH3r3/9y7V/zJgxXtAtXbrUa9KkiXfEEUd4xYsX9x5++OHQHZD1+VMQqBPpxx9/7Pbbc845JyGICMOJ9a233vIaNmzobdq0Kbbshx9+8Lp16+adeOKJ3sSJE70w+Omnn1zwoP99q1at8h555BEXMCn4DbKo7K9btmzx2rdv71133XWxc+Tjjz/unXXWWV7Lli296dOnh6KtUTjG5iRSGgKUb1W/fn13+btHjx725ptvusvfAwYMsO3bt1sY7N6927WrTp069tVXX7m2du3a1d5//30Lm6JFi7rLaz/99FNsWa1atax///52xhln2Ouvv27Tpk2zoNq7d69NnTrVqlevbt9++60NHDjQrr32Wnv22Wdty5YtFhbqNNDl73POOcdd6v/uu+/siy++sOuvv94WLVrknhOGnM/ChQu7XPM//vgjtn0bN25sQ4cOtfLly9vEiRNjjwVZsWLFbNWqVTZr1qzYMuXvdu/e3QYPHmwffvihvfbaaxZUUdlflXu9dOlSa9SokRUpUsQtu/zyy935skKFCjZkyBBbsGBBoNsalWNsjsrR8BmH1OrVqxPuf/DBB/v09O7evdulPgSVLpe+/vrrsfv//Oc/vRIlSoSup3fJkiXukvD111+/zzfyX375xaU7jB492gv6pbb47Xbrrbd6+fPnd70Q8T2FQZfclvnz58d6ztRr7/ck6fJ4kNOMlE7lX3lQe/zesRkzZnjFihXznn76aS/oNm7c6J1//vmuB/S3335LeEwpDm3btnU9pEG2YcOG0O+vW7du9c444wx3xUznxHhvvvmm6+X9z3/+E/he3qgcY3MKAW8epiBWB+B4e/bsSfiAxge969ev9/r37+/SHoJ8WSP5AOQHve+99567v2vXLvez8mCDQgcfra8utfkH4Geffdbl0d1///2uTfEuvfRSl9Mb5IOxJK//bbfdFjsgax/V488999w+wUUQ26b7/rZVXrYfROhnfS4V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DjvM/ay0A78nVgHp7t27XZBbvXp1t0y9vT71+u7YscP1ICdTL6x+LzN79+51Pb8lS5Z09y+99FKXenHXXXe53l31IE+cONFOO+009/hTTz1lVapUOch3BQByFj28AJBCSlNQyoJu3377retpPfPMM+2PP/7I0u83adLEBZsKcrt06WKPPfaY/f3331n63RYtWuz3OUpl8INdUUC+atUq9/Nvv/3mcn9btmwZe1xpD0cffXSW/j4A5BYCXgBIIeW+KoVBt+OOO84ef/xx19OrwDUr1POrVIL333/fGjRoYOPGjXMB5+LFi7P0t/enYMGCCfeV+6teXwAIEgJeAMhDFFAqnWHbtm37PFaoUCH3/549e/b5nRNPPNFuv/12mz17tnve66+/Hvud5OfnlFq1armA+Lvvvost27BhA6XNAOQ55PACQAopv3bFihXuZ6UiPPTQQ27w2rnnnrvPc5Wjq+BWOb5nnXWWy8+dN2+ey6k944wzXG7v9OnTXYWH+vXrx1ISJk+e7Aa6aSCcUg5yilIdevXqZYMHD3b5xfr7w4YNcwG71hMA8gp6eAEghT744AOXF6tbq1atXG/pK6+84kp7JatatarrxVV1hYoVK1r//v2tVKlS9vnnn7sAuG7dunbrrbfavffe6/KApW/fvi7FQfm6hx9+uKuokJPuu+8+a926tasdrHJo6mlWsK1SZwCQV6R5ql0DAEAOUP6xAnMF3ZdffnmqVwcAHFIaAADZppzh+fPnu0oNyt+944473PLzzz8/1asGADEEvACAgzJmzBiXI6wBcs2bN3eTT5QvXz7VqwUAMaQ0AAAAINQYtAYAAIBQI+AFAABAqBHwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAAWZv8PCO4KZMamVxAAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Example QUBO matrix (6-qubit), taken from the other QUBO notebook\n", "H1 = np.array([\n", " [2., 32., -32., -32., 32., 0.],\n", " [0., 1., 32., 0., -32., -32.],\n", " [0., 0., 35., 32., -64., -32.],\n", " [0., 0., 0., 2., -32., 32.],\n", " [0., 0., 0., 0., 35., 32.],\n", " [0., 0., 0., 0., 0., 4.]\n", "])\n", "\n", "# We'll group the 6 qubits into 2 groups of 3 qubits each: [3,3]\n", "# these combinations are also possible [2,2,2], [5,1], [4,2], [3,1,1] etc\n", "# different group combinations may have different alpha value or sample number needs so play around with different hyperparameters\n", "group_sizes = [3,3]\n", "\n", "# Single rotation layer: [\"Y\"]\n", "layers = [\"Y\"]\n", "\n", "# Run the optimization\n", "final_loss, best_bitstring, optimal_phases = optimize_qubo(\n", " qubo_matrix=H1,\n", " input_state=\"000000\", # e.g. 6-qubit input in |000000>\n", " group_sizes=group_sizes,\n", " layers=layers,\n", " sampling_size=10000000, # how many shots for sampling\n", " alpha=0.25, # CVaR parameter\n", " maxiter=600,\n", " verbose=False # displays iterations results if set to True\n", ")\n", "\n", "print(\"\\n=== Final Optimization Results ===\")\n", "print(f\"Final CVaR Loss: {final_loss}\")\n", "print(f\"Best Bitstring: {best_bitstring}\")\n", "print(f\"Optimal Phases: {optimal_phases}\")\n", "\n", "# Sample final circuit with the found phases, then plot the output distribution\n", "circ = build_circuit(list(optimal_phases), group_sizes, layers)\n", "circ.with_input(LogicalState(\"000000\"))\n", "sampler = pcvl.algorithm.Sampler(circ, max_shots_per_call=10000000)\n", "job_results = sampler.sample_count(10000000)\n", "\n", "output_dict = extract_probability_distribution(job_results, group_sizes)\n", "plot_bitstring_distribution(output_dict, title=\"Final Distribution After Optimization\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Explanation\n", "\n", "1. `optimize_qubo` calls our **objective function** repeatedly, adjusting phases to reduce the **CVaR** of the QUBO cost.\n", "2. `alpha` in **CVaR** picks how much “worst tail” of outcomes we average over. $\\alpha = 0.5$ is a middle ground.\n", "3. `best_bitstring` is chosen from the final distribution’s highest-probability outcome (in practice, you might also check the distribution).\n", "4. The **plot** helps visualize which bitstrings are being sampled at the end of optimization.\n", "\n", "---\n", "\n", "With this approach, you have a working **CVaR-VQE** routine for **QUBO** problems using an ansatz in the **QLOQ** framework. You can expand this by:\n", "- Increasing the number of **layers** (e.g., `[\"Y\",\"X\"]`).\n", "- Using **CX** gates instead of CZ inside groups (`ctype=\"cx\"`).\n", "- Changing **group_sizes** or QUBO matrix to match your real problem.\n", "- Exploring advanced optimization methods or different cost functions beyond QUBO.\n", "\n", "This completes our demonstration of a **QUBO** problem solved by a **CVar-VQE**-like approach in **Perceval’s QLOQ** environment. QLOQ can be applied to various problems involving a variational circuit so it is recommended to refactor this example to your needs whether by changing the loss function to match your given problem or the circuit structure itself." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 1 } ================================================ FILE: docs/source/notebooks/QUBO.ipynb ================================================ { "cells": [ { "attachments": {}, "cell_type": "markdown", "id": "349d0370", "metadata": {}, "source": [ "# The shortest path problem using QUBO\n", "This notebook implements a Perceval code for finding the shortest path on a _directed, weighted graph_ using the Quadratic Unconstrained Binary\n", "Optimization (QUBO) model. It is mainly implementing the algorithm in https://arxiv.org/pdf/2112.09766.pdf and represents the work done during the 2022 LOQCathon. \n", "\n", "Authors: Beata Zjawin, Benjamin Pointard, Nathan Claudet, Noé Delorme, Rina Ismailati.\n", "\n", "The task is to find the shortest path from start to finish (such that the sum of the weights is minimized) on a simple 5-edge graph:\n", "\n", "![Graph_5_edges](../_static/img/graph_5edges.png)\n", "\n", "Start with importing the necessary packages.\n" ] }, { "cell_type": "code", "execution_count": 11, "id": "ab150578", "metadata": {}, "outputs": [], "source": [ "import math\n", "\n", "import perceval as pcvl\n", "from perceval.components import PS, BS, GenericInterferometer\n", "import numpy as np\n", "from scipy.optimize import minimize\n", "import matplotlib.pyplot as plt\n", "plt.rcdefaults()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "7c25b79a-d88c-4bf2-9398-93c1434d06cd", "metadata": { "tags": [] }, "source": [ "## Implementation in Perceval\n", "To find the shortest path, first we need to compute the objective function of the weighted graph. The constraints specified by the shortest path problem can be written in the form of quadratic penalty functions, as required by the QUBO formalism [1]:\n", "\n", "\\begin{align*}\n", "H_s&=\\Big(\\sum_{j} x_{s,j} - \\sum_{k} x_{k,s} - 1\\Big)^2, \\\\\n", "H_f&=\\Big(\\sum_{j} x_{f,j} - \\sum_{k} x_{k,f} + 1\\Big)^2, \\\\\n", "H_i&=\\Big(\\sum_{j} x_{i,j} - \\sum_{k} x_{k,i}\\Big)^2. \\\\\n", "\\end{align*}\n", "\n", "Here, $s$ and $f$ correspond to the start and finish node, respectively. Together with the original objective function:\n", "\n", "\\begin{equation*}\n", "H_c= \\sum_{(i,j)\\in edges} c_{i,j} x^{2}_{i,j},\n", "\\end{equation*}\n", "\n", "the constraints form the final QUBO objective function:\n", "\n", "\\begin{equation*}\n", "H= \\alpha \\Big(H_s + H_f + \\sum_{i \\notin \\{s,f\\}} H_i\\Big) + H_c,\n", "\\end{equation*}\n", "\n", "where $\\alpha$ is a scaling factor satisfying $\\alpha > \\sum_{(i,j)\\in edges} c_{i,j}$.\n", "\n", "\n", "\n", "For our simple example, the objective function is given by (see the Appendix to generate Hamiltonians from arbitrary adjacency matrices):\n" ] }, { "cell_type": "code", "execution_count": 12, "id": "dcde3aa3-da33-4d7d-9846-7efa216550c6", "metadata": {}, "outputs": [], "source": [ "H1 = [[2., 32., -32., -32., 32., 0.],\n", " [0., 1., 32., 0., -32., -32.],\n", " [0., 0., 35., 32., -64., -32.],\n", " [0., 0., 0., 2., -32., 32.],\n", " [0., 0., 0., 0., 35., 32.],\n", " [0., 0., 0., 0., 0., 4.]]" ] }, { "attachments": {}, "cell_type": "markdown", "id": "6d0d9f59", "metadata": {}, "source": [ "Next, we implement a variational algorithm to find the optimal path using the QUBO model.\n", "\n", "Following the approach introduced in Ref. [2], the output of the simulation, i.e. the Fock states $|n> = |n_1,...,n_M>$, can be mapped into bit strings $b=(b_1,...b_M)$ encoding possible paths on the graph by applying the parity function:\n", "\n", "\\begin{equation*}\n", "\\rho_j: b_i^{(j)} = \\text{mod}[n_i,2] \\oplus j.\n", "\\end{equation*}\n", "\n", "For each evaluation, we need to test for $j=0$, $j=1$, $n=M$ and $n=M−1$, as it is not possible to know a priori which configuration is the optimal one. Here, the number of modes, $M$, corresponds to the number of possible paths and $n$ is the number of photons.\n", "\n", "In our example, $b=(b_{sa},b_{sb},b_{ab},b_{af},b_{ba},b_{bf})$, with $b_i \\in \\{0,1\\}$, where subscripts correspond to graph edges.\n", "\n", "In the cell below, we set up the functions necessary for the simulation, such as implementation of the parity function and sampling of the circuit." ] }, { "cell_type": "code", "execution_count": 13, "id": "3441d4b4", "metadata": {}, "outputs": [], "source": [ "def parify_samples(samples, j):\n", " \"\"\"apply the parity function to the samples\"\"\"\n", " def _parity(output, j):\n", " m = len(output)\n", " parity = [0]*m\n", " for i in range(0,m):\n", " parity[i] = (output[i] + j) % 2\n", " return pcvl.BasicState(parity)\n", " \n", " new_samples = pcvl.BSCount()\n", " for idx,sample in enumerate(samples):\n", " new_sample = _parity(sample,j)\n", " if new_sample in new_samples:\n", " new_samples[_parity(sample,j)] += samples[sample]\n", " else:\n", " new_samples[_parity(sample,j)] = samples[sample]\n", " return new_samples\n", " \n", "def set_parameters_circuit(parameters_circuit, values): \n", " \"\"\"set values of circuit parameters\"\"\"\n", " for idx, p in enumerate(parameters_circuit):\n", " parameters_circuit[idx].set_value(values[idx])\n", "\n", "def compute_samples(circuit, input_state, nb_samples, j):\n", " \"\"\"sample from the circuit\"\"\"\n", " p = pcvl.Processor(\"SLOS\", circuit)\n", " p.with_input(input_state)\n", " p.min_detected_photons_filter(0)\n", "\n", " sampler = pcvl.algorithm.Sampler(p) \n", " samples = sampler.sample_count(nb_samples)['results']\n", " \n", " return parify_samples(samples,j)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "bc3125ec-1fe5-4df4-a5bf-0cb9a7de5df0", "metadata": {}, "source": [ "The central part of solving the shortest path problem with Perceval is initializing a universal circuit and optimizing its parameters. We will work with the following generic interferometer:\n", "\n", "![generic_6photons_interferometer](../_static/img/generic_6photons_interferometer.svg)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "2bb3985b-3105-4d5b-893f-8d89f41d0051", "metadata": {}, "source": [ "We sample the circuit for all configurations of $j$ and $n$. In this example, we set all the initial parameters to $\\pi$, which is close to the optimal solution $-$ a good initial guess can dramatically improve simulation time. The loss function is given by\n", "\n", "$$E(j,\\theta,\\psi) = \\sum_{b^{(j)}} \\beta_{b^{(j)}} $$\n", "\n", "where $\\theta$ and $\\psi$ stand for the parameters of the linear interferometer: beam-splitters and phase-shifters, respectively. For optimization, we use the Powell minimisation algorithm from the scipy.optimize package." ] }, { "cell_type": "code", "execution_count": 14, "id": "d0d7c887", "metadata": {}, "outputs": [], "source": [ "def test_configuration(circuit, nb_modes, j, n, H, nb_samples):\n", " \"\"\"output the samples for a given configuration of j and n (includes minimisation of the loss function)\"\"\"\n", " parameters_circuit = circuit.get_parameters()\n", "\n", " input_state = pcvl.BasicState([1]*nb_modes)\n", " if n!=nb_modes:\n", " input_state = pcvl.BasicState([1]*(nb_modes-1)+[0])\n", "\n", " def loss(parameters):\n", " set_parameters_circuit(parameters_circuit, parameters)\n", " samples = compute_samples(circuit, input_state, nb_samples, j)\n", " E = 0\n", " for sample in samples:\n", " b = np.array([sample[i] for i in range(len(sample))])\n", " b_prime = np.dot(H, b)\n", " E += samples[sample]/nb_samples*np.dot(b.conjugate(), b_prime)\n", " \n", " return E.real\n", "\n", " # init_parameters = [2*(math.pi)*random.random() for _ in parameters_circuit] # initialize with random initial parameters\n", " init_parameters = [math.pi for _ in parameters_circuit] # initialize with a good guess\n", " best_parameters = minimize(loss, init_parameters, method='Powell', bounds=[(0,2*math.pi) for _ in init_parameters]).x \n", " set_parameters_circuit(parameters_circuit, best_parameters)\n", " samples = compute_samples(circuit, input_state, nb_samples, j)\n", " return samples\n", "\n", "def shortest_path(H, nb_samples):\n", " \"\"\"run the universal circuit and optimize the parameters\"\"\"\n", " nb_modes = len(H)\n", " circuit = GenericInterferometer(\n", " nb_modes,\n", " lambda i: BS(theta=pcvl.P(f\"theta{i}\"),phi_tr=pcvl.P(f\"phi_tr{i}\")),\n", " phase_shifter_fun_gen=lambda i: PS(phi=pcvl.P(f\"phi{i}\")))\n", " js = [0,1]\n", " ns = [nb_modes, nb_modes-1]\n", " configuration_samples = []\n", " for j in js:\n", " for n in ns:\n", " current_sample = test_configuration(circuit, nb_modes, j, n, H, nb_samples)\n", " print(f\"Configuration for (j,n)=({j},{n})\")\n", " print(current_sample)\n", " configuration_samples.append(([j,n],current_sample))\n", " \n", " return configuration_samples" ] }, { "attachments": {}, "cell_type": "markdown", "id": "75ab2dba-e47b-4d53-a92f-c13c077aeb69", "metadata": {}, "source": [ "Run the algorithm on 10000 samples (this may take a few minutes):" ] }, { "cell_type": "code", "execution_count": 15, "id": "7f440a3e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Configuration for (j,n)=(0,6)\n", "{\n", " |1,1,0,0,0,0>: 1347\n", " |1,0,1,0,0,0>: 1186\n", " |0,0,0,0,0,0>: 33\n", " |0,1,0,1,0,0>: 898\n", " |0,1,0,0,1,0>: 1048\n", " |0,1,0,0,0,1>: 992\n", " |0,0,1,1,0,0>: 888\n", " |0,0,1,0,1,0>: 908\n", " |0,0,1,0,0,1>: 888\n", " |0,0,0,0,1,1>: 15\n", " |1,0,0,0,0,1>: 24\n", " |1,1,1,0,0,1>: 3\n", " |1,1,0,1,1,0>: 540\n", " |1,1,0,1,0,1>: 8\n", " |1,1,0,0,1,1>: 4\n", " |1,0,0,1,0,0>: 3\n", " |1,0,1,1,1,0>: 530\n", " |1,0,1,1,0,1>: 10\n", " |1,0,0,1,1,1>: 9\n", " |0,1,1,1,1,0>: 3\n", " |0,1,1,0,0,0>: 5\n", " |0,1,0,1,1,1>: 305\n", " |0,0,0,1,1,0>: 5\n", " |0,0,1,1,1,1>: 312\n", " |1,0,0,0,1,0>: 7\n", " |1,1,1,0,1,0>: 1\n", " |1,1,1,1,1,1>: 1\n", " |1,0,1,0,1,1>: 3\n", " |0,1,1,1,0,1>: 3\n", " |0,1,1,0,1,1>: 6\n", " |0,0,0,1,0,1>: 15\n", "}\n", "Configuration for (j,n)=(0,5)\n", "{\n", " |1,1,1,0,0,0>: 2\n", " |1,1,0,1,0,0>: 3\n", " |1,1,0,0,1,0>: 2\n", " |1,0,0,0,0,0>: 39\n", " |1,0,0,1,1,0>: 17\n", " |0,0,0,1,0,0>: 16\n", " |0,0,1,0,0,0>: 23\n", " |0,0,1,1,1,0>: 4\n", " |0,0,0,0,1,0>: 18\n", " |0,0,0,0,0,1>: 4\n", " |0,1,1,0,0,1>: 32\n", " |0,1,0,0,0,0>: 6554\n", " |0,1,0,1,1,0>: 3227\n", " |0,1,0,1,0,1>: 11\n", " |0,1,0,0,1,1>: 27\n", " |0,1,1,1,0,0>: 5\n", " |0,1,1,0,1,0>: 10\n", " |0,1,1,1,1,1>: 6\n", "}\n", "Configuration for (j,n)=(1,6)\n", "{\n", " |0,0,0,1,1,0>: 6\n", " |0,1,0,1,0,0>: 1\n", " |1,0,0,1,0,0>: 9979\n", " |1,1,0,0,0,0>: 2\n", " |1,1,1,1,0,0>: 1\n", " |1,0,1,0,0,0>: 3\n", " |1,0,0,0,0,1>: 7\n", " |1,0,1,1,0,1>: 1\n", "}\n", "Configuration for (j,n)=(1,5)\n", "{\n", " |1,1,0,0,1,0>: 2\n", " |0,1,0,0,1,1>: 3\n", " |0,1,0,1,1,0>: 9950\n", " |0,1,0,0,0,0>: 26\n", " |0,1,0,1,0,1>: 3\n", " |0,1,1,1,0,0>: 1\n", " |1,0,0,1,1,0>: 15\n", "}\n" ] } ], "source": [ "nb_samples = 10000\n", "data = shortest_path(H1, nb_samples)" ] }, { "cell_type": "markdown", "id": "308e2867", "metadata": {}, "source": [ "Plot the results for better visualization:" ] }, { "cell_type": "code", "execution_count": 16, "id": "f29aa6de", "metadata": {}, "outputs": [], "source": [ "def plot_samples(samples):\n", " \n", " values = list(samples.values())\n", "\n", " keys = samples.keys()\n", "\n", " key_list = []\n", " for x in keys:\n", " s = \"\".join(str(c) for c in list(x))\n", " key_list.append(s)\n", "\n", " y_pos = np.arange(len(key_list))\n", " barlist = plt.bar(y_pos, values, align='center', alpha=0.8)\n", " index = key_list.index('100100')\n", " barlist[index].set_color('m')\n", " plt.yscale('log')\n", " plt.xticks(y_pos, key_list)\n", " plt.xticks(rotation = 70)\n", " plt.ylabel('Sample values')\n", " plt.title('Sampling the Fock States')\n", "\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": 17, "id": "f13d9a95", "metadata": {}, "outputs": [ { "data": { "image/png": "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WLRoEQoKChAcHKzT+4aGhiI0NBS5ubmwtbXVdRhERERUDUledOLi4tC7d2/N47CwMABAUFAQIiMjMXLkSNy6dQuzZ89Geno6PDw8sH///nITlImIiIgeJXnR8fPze+otGyZNmoRJkyY9p0REREQkF9V6jk5V4hwdIiIi+TPaohMaGopz584hNjZW6ihERERURYy26BAREZH8segQERGRbLHoEBERkWwZbdHhZGQiIiL5M9qiw8nIRERE8me0RYeIiIjkj0WHiIiIZItFh4iIiGTLaIsOJyMTERHJn9EWHU5GJiIikj+jLTpEREQkfyw6REREJFssOkRERCRbLDpEREQkWyw6REREJFtGW3R4ejkREZH8GW3R4enlRERE8me0RYeIiIjkj0WHiIiIZItFh4iIiGSLRYeIiIhki0WHiIiIZItFh4iIiGTLaIsOr6NDREQkf0ZbdHgdHSIiIvnTuuisXbsWe/fu1Tx+//33UadOHXTv3h0pKSl6DUdERESkC62LzhdffIGaNWsCAGJiYqBUKvH111/Dzs4O06ZN03tAIiIiosqqoe1fSE1NRcuWLQEAO3bswLBhwzB+/Hj06NEDfn5++s5HREREVGla79GxtrbG7du3AQC//fYb+vbtCwCwtLTEvXv39JuOiIiISAda79Hp27cv3n77bXTq1AmXLl1C//79AQB///03nJ2d9Z2PiIiIqNK03qOjVCrh4+ODW7du4eeff0b9+vUBAPHx8Xj99df1HpCIiIiosrTeo1OnTh0sWbKk3PI5c+boJRARERGRvlTqOjpHjhzB6NGj0b17d6SlpQEAfvzxRxw9elSv4YiIiIh0oXXR+fnnnxEQEICaNWsiISEBRUVFAICcnBx88cUXeg9IREREVFlaF525c+di+fLlWLlyJczMzDTLe/TogYSEBL2Gq0q8BQQREZH8aV10Ll68iF69epVbbmtri7t37+oj03PBW0AQERHJn9ZFp1GjRkhKSiq3/OjRo3BxcdFLKCIiIiJ90LrohISEYOrUqfjf//4HhUKBGzduYMOGDZg+fTreeeedqshIREREVClan14+c+ZMqNVqvPjiiygsLESvXr1gYWGB6dOnY/LkyVWRkYiIiKhStC46CoUCH330EWbMmIGkpCTk5+fD1dUV1tbWVZGPiIiIqNK0LjqlzM3N4erqqs8sRERERHql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Zyo62lyQj4iIiKi6MJxZlURERERaqlTRiYqKwsCBA9GiRQu0aNECAwcOxMGDB/WdrUrxgoFERETyp3XRWbp0KV566SXY2Nhg6tSpmDp1KmrXro3+/ftDqVRWRcYqwQsGEhERyZ/Wp5d/8cUXWLhwISZNmqRZNmXKFPTo0QNffPEFQkND9RqQiIiIqLK03qNz9+5dvPTSS+WW9+vXDzk5OXoJRURERKQPWhedl19+Gb/88ku55Tt37sTAgQP1EoqIiIhIH7Q+dOXq6orPP/8chw8f1txB/MSJEzh27Bjee+89fPfdd5rnTpkyRX9J9YxXRiYiIpI/re911bx582d7YYUCV69erVSo54n3uqJnwXtdERFVL1V2r6vk5GSdghERERE9L4bzX1MiIiIiLWm9R0cIgW3btuGPP/5AZmZmudtDbN++XW/hiIiIiHShddF59913sWLFCvTu3Rv29vZQKBRVkYuIiIhIZ1oXnR9//BHbt29H//79qyIPERERkd5oPUfH1tYWLi4uVZHlueK9roiIiORP66LzySefYM6cObh3715V5HlueK8rIiIi+dP60NWIESOwceNGNGzYEM7OzjAzMyuzPiEhQW/hiIiIiHShddEJCgpCfHw8Ro8ezcnIREREVK1pXXT27t2LAwcO4IUXXqiKPERERER6o/UcHUdHR94qgYiIiAyC1kVnwYIFeP/993Ht2rUqiENERESkP1ofuho9ejQKCwvRokUL1KpVq9xk5OzsbL2FIyIiItKF1kVn0aJFVRDj+VMqlVAqlVCpVFJHISIioiqiEEIIqUNI6Vlv807GreBCAU4HnIapjSlMLA3nXrjq+2qo8lToeKAjrNpaSR2HiEhvnvXfb6336Dzs/v37KC4uLrOMZYGIiIiqC63/a1pQUIBJkyahYcOGsLKyQt26dct8EREREVUXWhed999/H4cOHcKyZctgYWGBVatWYc6cOXBwcMC6deuqIiMRERFRpWh96Gr37t1Yt24d/Pz8EBwcjJ49e6Jly5ZwcnLChg0bMGrUqKrISURERKQ1rffoZGdna+5eXrt2bc3p5C+88AKio6P1m46IiIhIB1oXHRcXFyQnJwMA2rZtiy1btgD4d09PnTp19BqOiIiISBdaF53g4GCcOnUKADBz5kwolUpYWlpi2rRpmDFjht4DEhEREVWW1nN0pk2bpvmzv78/zp8/j4SEBLRs2RIdO3bUazgiIiIiXeh0HR0AcHZ2hrOzsx6iEBEREenXMx+6iomJwZ49e8osW7duHZo3b46GDRti/PjxKCoq0nvAqqJUKuHq6govLy+poxAREVEVeeai8+mnn+Lvv//WPD5z5gzGjRsHf39/zJw5E7t378a8efOqJGRVCA0Nxblz5xAbGyt1FCIiIqoiz1x0EhMT8eKLL2oeb9q0Cd7e3li5ciXCwsLw3Xffac7AIiIiIqoOnrno3LlzB/b29prHf/75JwIDAzWPvby8kJqaqt90RERERDp45qJjb2+vuX5OcXExEhIS0K1bN836vLw8mJmZ6T8hERERUSU9c9Hp378/Zs6ciSNHjmDWrFmoVasWevbsqVl/+vRptGjRokpCEhEREVXGM59e/tlnn2Ho0KHw9fWFtbU11q5dC3Nzc836NWvWoF+/flUSkoiIiKgynrno2NnZITo6Gjk5ObC2toapqWmZ9Vu3boW1tbXeAxIRERFVltYXDLS1ta1web169XQOQ0RERKRPWt/rioiIiMhQsOgQERGRbLHoEBERkWyx6BAREZFssegQERGRbLHoEBERkWwZbdFRKpVwdXWFl5eX1FGIiIioihht0QkNDcW5c+cQGxsrdRQiIiKqIkZbdIiIiEj+WHSIiIhItlh0iIiISLZYdIiIiEi2WHSIiIhItlh0iIiISLZYdIiIiEi2WHSIiIhItlh0iIiISLZYdIiIiEi2WHSIiIhItlh0iIiISLZYdIiIiEi2WHSIiIhItlh0iIiISLZYdIiIiEi2WHSIiIhItmRRdIYMGYK6deti+PDhUkchIiKiakQWRWfq1KlYt26d1DGIiIiompFF0fHz84ONjY3UMYiIiKiakbzoREdHY9CgQXBwcIBCocCOHTvKPUepVMLZ2RmWlpbw9vbGyZMnn39QIiIiMjiSF52CggK4u7tDqVRWuH7z5s0ICwtDeHg4EhIS4O7ujoCAAGRmZj7npERERGRoakgdIDAwEIGBgY9d/+233yIkJATBwcEAgOXLl2Pv3r1Ys2YNZs6cqfX7FRUVoaioSPM4NzdX+9BERERkECTfo/MkxcXFiI+Ph7+/v2aZiYkJ/P39ERMTU6nXnDdvHmxtbTVfjo6O+opLRERE1Uy1LjpZWVlQqVSwt7cvs9ze3h7p6emax/7+/nj11Vexb98+NG3a9IklaNasWcjJydF8paamVll+IiIikpbkh6704eDBg8/8XAsLC1hYWFRhGiIiIqouqvUeHTs7O5iamiIjI6PM8oyMDDRq1EiiVERERGQoqnXRMTc3h6enJ6KiojTL1Go1oqKi4OPjo9NrK5VKuLq6wsvLS9eYREREVE1JfugqPz8fSUlJmsfJyclITExEvXr10KxZM4SFhSEoKAhdunRB165dsWjRIhQUFGjOwqqs0NBQhIaGIjc3F7a2troOg4iIiKohyYtOXFwcevfurXkcFhYGAAgKCkJkZCRGjhyJW7duYfbs2UhPT4eHhwf2799fboIyERER0aMkLzp+fn4QQjzxOZMmTcKkSZOeUyIiIiKSi2o9R6cqcY4OERGR/Blt0QkNDcW5c+cQGxsrdRQiIiKqIkZbdIiIiEj+WHSIiIhItlh0iIiISLYkP+tKKkqlEkqlEiqVSuooRESkg0HfH5U6QqXsnvyC1BGMgtHu0eFkZCIiIvkz2qJDRERE8seiQ0RERLLFokNERESyxaJDREREssWzrqrwrCueCUBERCQto92jw7OuiIiI5M9oiw4RERHJH4sOERERyRaLDhEREckWiw4RERHJltEWHaVSCVdXV3h5eUkdhYiIiKqI0RYdnnVFREQkf0ZbdIiIiEj+WHSIiIhItlh0iIiISLZYdIiIiEi2WHSIiIhItlh0iIiISLaMtujwOjpERETyZ7RFh9fRISIikj+jLTpEREQkfyw6REREJFssOkRERCRbLDpEREQkWyw6REREJFssOkRERCRbLDpEREQkWyw6REREJFssOkRERCRbNaQOIBWlUgmlUgmVSiV1FCKiKjHo+6NSR6iU3ZNfkDoCyYjR7tHhLSCIiIjkz2iLDhEREckfiw4RERHJFosOERERyRaLDhEREckWiw4RERHJFosOERERyRaLDhEREckWiw4RERHJFosOERERyRaLDhEREckWiw4RERHJFosOERERyRaLDhEREclWDakDSEWpVEKpVEKlUkkdhahaGfT9UakjVMruyS9IHYGIqiGj3aMTGhqKc+fOITY2VuooREREVEWMtugQERGR/LHoEBERkWyx6BAREZFssegQERGRbLHoEBERkWyx6BAREZFssegQERGRbLHoEBERkWyx6BAREZFssegQERGRbLHoEBERkWyx6BAREZFs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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plot_samples(data[2][1]) # plot the samples for (j,n) = 1,6, i.e. the configuration which contains both a physical and minimum-energy solution" ] }, { "attachments": {}, "cell_type": "markdown", "id": "cd31e406-cf82-4ef2-8df0-a17cf06a8d15", "metadata": {}, "source": [ "We can see that the best path is $b=(1,0,0,1,0,0)$, that is, the path $saf$." ] }, { "cell_type": "markdown", "id": "fbb8e055-474a-4456-823f-9348e06c6176", "metadata": {}, "source": [ "## Bibliography\n", "[1] Thomas Krauss and Joey McCollum. “Solving the Network Shortest Path Problem on a Quantum Annealer”. In: IEEE Transactions on Quantum Engineering 1 (2020), pp. 1–12. doi: 10.1109/TQE.2020.3021921.\n", "\n", "[2] Kamil Bradler and Hugo Wallner. Certain properties and applications of\n", "shallow bosonic circuits. 2021. doi: 10.48550/ARXIV.2112.09766. url:\n", "https://arxiv.org/abs/2112.09766." ] }, { "attachments": {}, "cell_type": "markdown", "id": "3065aa56-30e8-4aee-8b6c-f8aff1615c01", "metadata": {}, "source": [ "## Appendix\n", "If you want to try more complicated graphs, this code generates the objective function for any weighted, directed graph:" ] }, { "cell_type": "code", "execution_count": 18, "id": "dcfdc370", "metadata": {}, "outputs": [], "source": [ "def alpha_graph(graph): \n", " \"\"\"calculate alpha as sum of all the costs + 1\"\"\"\n", " alpha = 0 \n", " for i in range(0,len(graph)):\n", " for j in range(0,len(graph)):\n", " if graph[i][j] != 0 : alpha+=graph[i][j]\n", " return alpha+1\n", "\n", "def graph_to_s(graph, s, weight):\n", " \"\"\"calculate Hs\"\"\"\n", " ham = np.zeros((weight, weight))\n", " edges_coeff = [0]*weight\n", " constant_coeff = -1\n", " count = 0\n", " for i in range(0, len(graph)):\n", " for j in range(0, len(graph)):\n", " if graph[i][j] != 0 :\n", " if i == s:\n", " edges_coeff[count] = 1\n", " if j == s:\n", " edges_coeff[count] = -1\n", " count += 1\n", " \n", " for i in range(0, weight):\n", " for j in range(i+1, weight):\n", " ham[i][j] = edges_coeff[i]*edges_coeff[j]*2\n", " \n", " for i in range(0, weight):\n", " ham[i][i] += edges_coeff[i]*edges_coeff[i] + constant_coeff*edges_coeff[i]*2 \n", "\n", " return ham\n", "\n", "def graph_to_f(graph, t, weight):\n", " \"\"\"calculate Hf\"\"\"\n", " ham = np.zeros((weight, weight))\n", " edges_coeff = [0]*weight\n", " constant_coeff = +1\n", " count = 0\n", " for i in range(0,len(graph)):\n", " for j in range(0,len(graph)):\n", " if graph[i][j] != 0 :\n", " if i == t:\n", " edges_coeff[count] = 1\n", " if j == t:\n", " edges_coeff[count] = -1\n", " count += 1\n", " \n", " for i in range(0, weight):\n", " for j in range(i+1, weight):\n", " ham[i][j] = edges_coeff[i]*edges_coeff[j]*2\n", " \n", " for i in range(0,weight):\n", " ham[i][i] += edges_coeff[i]*edges_coeff[i] + constant_coeff*edges_coeff[i]*2 \n", "\n", " return ham\n", "\n", "def graph_to_i(graph, our_i, weight):\n", " \"\"\"calculate Hi\"\"\"\n", " ham = np.zeros((weight, weight))\n", " edges_coeff = [0]*weight\n", " count = 0\n", " for i in range(0, len(graph)):\n", " for j in range(0, len(graph)):\n", " if graph[i][j] != 0 :\n", " if i==our_i:\n", " edges_coeff[count] = 1\n", " if j==our_i:\n", " edges_coeff[count] = -1\n", " count += 1\n", " \n", " for i in range(0, weight):\n", " for j in range(i+1, weight):\n", " ham[i][j] = edges_coeff[i]*edges_coeff[j]*2 \n", " \n", " for i in range(0, weight):\n", " ham[i][i] += edges_coeff[i]*edges_coeff[i]\n", "\n", " return ham\n", "\n", "def graph_to_c(graph, weight):\n", " \"\"\"calculate Hc\"\"\"\n", " ham = np.zeros((weight, weight))\n", " count = 0\n", " for i in range(0,len(graph)):\n", " for j in range(0,len(graph)):\n", " if graph[i][j] != 0 :\n", " ham[count][count] = graph[i][j]\n", " count += 1\n", "\n", " return ham\n", "\n", "def graph_to_hamiltonian(graph,s,f): \n", " \"\"\"returns the graph adjacency matrix as a Hamiltonian\"\"\"\n", " weight = np.count_nonzero(graph)\n", " ham = np.zeros((weight, weight))\n", " alpha = alpha_graph(graph)\n", " ham += alpha*graph_to_s(graph, s, weight)\n", " ham += alpha*graph_to_f(graph, f, weight)\n", " for i in range(0,weight):\n", " if i!=s and i!=f:\n", " ham += alpha*graph_to_i(graph, i, weight)\n", " ham += graph_to_c(graph, weight)\n", "\n", " return ham" ] }, { "cell_type": "code", "execution_count": 19, "id": "7bbfb18f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 2. 32. -32. -32. 32. 0.]\n", " [ 0. 1. 32. 0. -32. -32.]\n", " [ 0. 0. 35. 32. -64. -32.]\n", " [ 0. 0. 0. 2. -32. 32.]\n", " [ 0. 0. 0. 0. 35. 32.]\n", " [ 0. 0. 0. 0. 0. 4.]]\n" ] } ], "source": [ "\"\"\"example: our 5-edge graph\"\"\"\n", "# start,a,b,finish -> 0,1,2,3\n", "G1 = np.array([[0,2,1,0], [0,0,3,2], [0,3,0,4], [0,0,0,0]]) # weights associated to all possible paths\n", "G1_s = 0 # start index\n", "G1_f = 3 # finish index\n", "H_example = graph_to_hamiltonian(G1, G1_s, G1_f)\n", "print(H_example)" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Quantum_teleportation_feed_forward.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "7d428b7117a2a2fe", "metadata": {}, "source": [ "# Quantum teleportation using feed-forward" ] }, { "cell_type": "markdown", "id": "61dad5341013c224", "metadata": {}, "source": [ "The goal of this notebook is to use perceval's feed-forward ability to demonstrate the quantum teleportation algorithm \\[1\\] on a photonic simulated circuit using dual rail encoding." ] }, { "cell_type": "markdown", "id": "1916284b04197f03", "metadata": {}, "source": [ "## I. Definition of the problem" ] }, { "cell_type": "markdown", "id": "60118172", "metadata": {}, "source": [ "The idea of the protocol is the following:\n", "\n", "Say that Alice has a generic qubit of the form\n", "\n", "$$|\\psi\\rangle = \\alpha |0\\rangle + \\beta |1\\rangle \\;$$\n", "\n", "that they want to send to a distant receiver called Bob. Since Bob is distant, we want to avoid transporting physical systems from Alice to Bob (only classical light will do).\n", "\n", "Before the start of the algorithm, Alice and Bob need to share a maximally entangled Bell state. For this example, we choose\n", "\n", "$$|\\phi^+\\rangle = \\frac{1}{\\sqrt{2}} (|0_A 0_B\\rangle + |1_A 1_B\\rangle) \\;$$\n", "\n", "The first qubit is accessible to Alice and the second to Bob. We now drop the subscript $A$ and $B$ for clarity. \n", "\n", "The composite system is then\n", "\n", "$$|\\psi\\rangle \\otimes |\\phi^+\\rangle = (\\alpha |0\\rangle + \\beta |1\\rangle) \\otimes \\frac{1}{\\sqrt{2}} (|00\\rangle + |11\\rangle)$$\n", "\n", "The algorithm is the following:\n", "\n", "Alice performs a CNOT using the first qubit as control and the second as target, then applies a Hadamard gate to the first qubit.\n", "\n", "![quantum_teleportation_circuit](../_static/img/quantum_teleportation_circuit.jpg)\n", "\n", "At the end, the composite system can be written as\n", "\n", "\\begin{align}\n", "& \\frac{1}{2}|00\\rangle \\otimes (\\alpha |0\\rangle + \\beta |1\\rangle) \\\\\n", "+& \\frac{1}{2}|01\\rangle \\otimes (\\beta |0\\rangle + \\alpha |1\\rangle) \\\\\n", "+& \\frac{1}{2}|10\\rangle \\otimes (\\alpha |0\\rangle - \\beta |1\\rangle) \\\\\n", "+& \\frac{1}{2}|11\\rangle \\otimes (- \\beta |0\\rangle + \\alpha |1\\rangle)\n", "\\end{align}\n", "\n", "Then Alice measures their two qubits and send the results to Bob using a classical channel.\n", "Firstly, if the second qubit is measured to be 1, Bob needs to apply a X gate to their qubit.\n", "Then, if the first qubit is measured to be 1, Bob needs to apply a Z gate to their qubit.\n", "\n", "After these corrections, Bob's qubit is guaranteed to be the original qubit of Alice $|\\psi\\rangle$." ] }, { "cell_type": "markdown", "id": "57318a20fc7eeb69", "metadata": {}, "source": [ "## II. Translation to Perceval" ] }, { "cell_type": "code", "execution_count": 1, "id": "4ec2a415e1cb1f04", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "import perceval as pcvl\n", "from perceval import catalog, Experiment" ] }, { "cell_type": "markdown", "id": "7208ddca25dafd45", "metadata": {}, "source": [ "### Starting state" ] }, { "cell_type": "markdown", "id": "17b862dbe9a08975", "metadata": {}, "source": [ "First, we need to create the input state $|\\psi\\rangle \\otimes |\\phi^+\\rangle$ for this algorithm. For demonstration purpose, we choose $\\alpha$ and $\\beta$ randomly." ] }, { "cell_type": "code", "execution_count": 2, "id": "initial_id", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.672*|1,0>+(-0.112-0.732I)*|0,1>\n" ] } ], "source": [ "# Creation of the qubit to transmit\n", "alpha = np.random.random()\n", "beta = np.random.random() * np.exp(2 * np.pi * 1j * np.random.random())\n", "# alpha |0> + beta |1> in dual rail encoding\n", "to_transmit = pcvl.BasicState([1, 0]) * alpha + pcvl.BasicState([0, 1]) * beta\n", "to_transmit.normalize()\n", "\n", "alpha = to_transmit[pcvl.BasicState([1, 0])] # Normalized\n", "beta = to_transmit[pcvl.BasicState([0, 1])]\n", "\n", "print(to_transmit)" ] }, { "cell_type": "code", "execution_count": 3, "id": "b480ea69ffc07543", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.707*|1,0,1,0>+0.707*|0,1,0,1>\n" ] } ], "source": [ "# Creation of the quantum channel\n", "sg = pcvl.StateGenerator(pcvl.Encoding.DUAL_RAIL)\n", "bell_state = sg.bell_state(\"phi+\")\n", "print(bell_state)" ] }, { "cell_type": "code", "execution_count": 4, "id": "41cbc6dac613658f", "metadata": {}, "outputs": [], "source": [ "input_state = to_transmit * bell_state" ] }, { "cell_type": "markdown", "id": "884bebccd4e671b9", "metadata": {}, "source": [ "### Tomography" ] }, { "cell_type": "markdown", "id": "6a78aa77d2475c6b", "metadata": {}, "source": [ "Since we will only return probabilities and not quantum amplitudes, we will not have access to the relative phase between $|0\\rangle$ and $|1\\rangle$. However, we saw in the paragraph that, we need correction to teleport the state. Because we want to see the correctness of the teleportation, we code below a state tomography for 1 qubit to be able to make we have teleported the correct state.\n", "\n", "Since a qubit is defined up to a global rotation, we consider that $\\alpha$ is a real non-negative number." ] }, { "cell_type": "code", "execution_count": 5, "id": "5bcf056c9f390892", "metadata": {}, "outputs": [], "source": [ "from perceval import Processor\n", "\n", "\n", "# Needed if the number of modes is bigger than 2\n", "def squash_results(res: pcvl.BSDistribution, first_mode: int) -> pcvl.BSDistribution:\n", " \"\"\"Sum the output probabilities to keep only the mode of interest and the following\"\"\"\n", " bsd = pcvl.BSDistribution()\n", " for state, prob in res.items():\n", " bsd[state[first_mode:first_mode+2]] += prob\n", "\n", " return bsd\n", "\n", "def tomography(exp: pcvl.Experiment, first_mode: int = 0) -> pcvl.StateVector:\n", " # First using identity, we get alpha ** 2 and |beta| ** 2\n", " processor = Processor(\"SLOS\", exp)\n", " res = processor.probs()[\"results\"]\n", " res = squash_results(res, first_mode)\n", "\n", " alpha = res[pcvl.BasicState([1, 0])] ** .5\n", " if alpha == 0:\n", " return pcvl.StateVector(pcvl.BasicState([0, 1]))\n", "\n", " exp = exp.copy()\n", " # We do the same, but we add a H gate at the end for the qubit we are interested in\n", " exp.add(first_mode, pcvl.BS.H())\n", " processor = Processor(\"SLOS\", exp)\n", " res = processor.probs()[\"results\"]\n", " res = squash_results(res, first_mode)\n", "\n", " p0 = res[pcvl.BasicState([1, 0])] # 1/2 |alpha + beta| ** 2\n", " p1 = res[pcvl.BasicState([0, 1])] # 1/2 |alpha - beta| ** 2\n", "\n", " # By writing beta = x + i y, we get\n", " x = (p0 - p1) / (2 * alpha)\n", "\n", " exp = exp.copy()\n", " # We do the same, but we multiply by i the amplitudes of qubit |1> before applying the H gate\n", " exp.add(first_mode + 1, pcvl.PS(np.pi / 2))\n", " exp.add(first_mode, pcvl.BS.H())\n", " processor = Processor(\"SLOS\", exp)\n", " res = processor.probs()[\"results\"]\n", " res = squash_results(res, first_mode)\n", "\n", " p0 = res[pcvl.BasicState([1, 0])] # 1/2 |alpha + i beta| ** 2\n", " p1 = res[pcvl.BasicState([0, 1])] # 1/2 |alpha - i beta| ** 2\n", "\n", " y = (p0 - p1) / (2 * alpha)\n", " beta = x + 1j * y\n", "\n", " return alpha * pcvl.BasicState([1, 0]) + beta * pcvl.BasicState([0, 1])" ] }, { "cell_type": "markdown", "id": "44ad7639024e5118", "metadata": {}, "source": [ "We can now test this algorithm on our original qubit using an identity circuit." ] }, { "cell_type": "code", "execution_count": 6, "id": "937d6da2124fc11c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.672*|1,0>+(-0.112-0.732I)*|0,1>" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "experiment = Experiment(2)\n", "\n", "experiment.min_detected_photons_filter(1)\n", "experiment.with_input(to_transmit)\n", "\n", "tomography(experiment)" ] }, { "cell_type": "markdown", "id": "3597044aedd3249c", "metadata": {}, "source": [ "We get the same state so the tomography process works." ] }, { "cell_type": "markdown", "id": "13cf7a685228ff18", "metadata": {}, "source": [ "### Circuit" ] }, { "cell_type": "markdown", "id": "f18e201949542957", "metadata": {}, "source": [ "Now we need to define the circuit on which the operations will take place. Since we need to use quantum gates and feed-forward operations, we need to use a `Processor` object.\n", "\n", "First, we define the photonic circuit that applies on the qubits. We have 3 qubits hence we need a processor with 6 modes.\n", "\n", "Since the qubits on which the CNOT is applied will only perform 1-qubit gates *in the quantum circuit*, we can use a postprocessed CNOT instead of a heralded CNOT." ] }, { "cell_type": "code", "execution_count": 7, "id": "46c1994bcba8184f", "metadata": {}, "outputs": [], "source": [ "experiment = Experiment(6)\n", "experiment.add(0, catalog[\"postprocessed cnot\"].build_processor())\n", "experiment.add(0, pcvl.BS.H());" ] }, { "cell_type": "markdown", "id": "2a6e8e7857d28509", "metadata": {}, "source": [ "Now we need to add the feed-forwarded components. For this purpose, Perceval uses two configurators that link measures to circuits or processors.\n", "\n", "Both of them need to be defined by the number of modes they measure, the number of empty modes between the measured modes and the circuit they configure (this is an integer called `offset`), and a default configuration that is used whenever a measure does not befall into one of the cases that were defined when creating the object.\n", "\n", "The measured modes need to be classical modes. Thus, we need to add detectors before adding the configurators.\n", "\n", "The X gate corresponds to a permutation for a dual rail encoding if we measure $|1\\rangle$, or an empty circuit if we measure $|0\\rangle$. Thus, we are going to use a `FFCircuitProvider` as it links a measured state to a circuit or a processor." ] }, { "cell_type": "code", "execution_count": 8, "id": "84ab63365eb0278f", "metadata": {}, "outputs": [], "source": [ "# 2 measured modes\n", "# offset = 0 means that there is 0 empty modes between the measured modes and the circuit\n", "# the default circuit is an empty circuit\n", "ff_X = pcvl.FFCircuitProvider(2, 0, pcvl.Circuit(2))\n", "\n", "# Now if we measure a logical state |1>, we need to perform a permutation of the modes\n", "ff_X.add_configuration([0, 1], pcvl.PERM([1, 0]))\n", "\n", "# Add perfect detectors to the modes that will be measured\n", "experiment.add(2, pcvl.Detector.pnr())\n", "experiment.add(3, pcvl.Detector.pnr())\n", "experiment.add(2, ff_X);" ] }, { "cell_type": "markdown", "id": "4ab9a62af4dff3cc", "metadata": {}, "source": [ "The Z gate corresponds to a $\\pi$ shift on the second mode. Thus, we are going to use a `FFConfigurator` that uses a parametrized circuit and links the measured states to a mapping of values for these parameters." ] }, { "cell_type": "code", "execution_count": 9, "id": "34c780e885758455", "metadata": {}, "outputs": [], "source": [ "phi = pcvl.P(\"phi\")\n", "# Like Circuits and Processors, we can chain the `add` methods\n", "ff_Z = pcvl.FFConfigurator(2, 3, pcvl.PS(phi), {\"phi\": 0}).add_configuration([0, 1], {\"phi\": np.pi})\n", "\n", "experiment.add(0, pcvl.Detector.pnr())\n", "experiment.add(1, pcvl.Detector.pnr())\n", "experiment.add(0, ff_Z);" ] }, { "cell_type": "markdown", "id": "88f450e84ce7f9a2", "metadata": {}, "source": [ "We can check that we defined correctly our processor. Note that using the `recursive=True` flag, we can expose the inner circuit of the `FFConfigurator`." ] }, { "cell_type": "code", "execution_count": 10, "id": "cd64eca26d10b8cd", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PNR\n", "\n", "\n", "\n", "PNR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "FFC\n", "\n", "\n", "\n", "U(FFC)\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "PNR\n", "\n", "\n", "\n", "PNR\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "FFC\n", "\n", "\n", "Φ=phi\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald2]\n", "0\n", "\n", "[herald3]\n", "0\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pcvl.pdisplay(experiment, recursive=True)" ] }, { "cell_type": "markdown", "id": "be92f0ddd80ad218", "metadata": {}, "source": [ "## III. Simulation" ] }, { "cell_type": "markdown", "id": "5324d67209a5f0ed", "metadata": {}, "source": [ "Now that we have both the input state and the processor, we can run the algorithm and check that it works." ] }, { "cell_type": "code", "execution_count": 11, "id": "a438b878ced2cfe8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'results': {\n", "\t|1,0,0,1,1,0>: 0.11298990136411816\n", "\t|1,0,1,0,1,0>: 0.11298990136411817\n", "\t|1,0,1,0,0,1>: 0.1370100986358818\n", "\t|0,1,1,0,1,0>: 0.11298990136411816\n", "\t|0,1,1,0,0,1>: 0.1370100986358818\n", "\t|1,0,0,1,0,1>: 0.1370100986358818\n", "\t|0,1,0,1,1,0>: 0.11298990136411816\n", "\t|0,1,0,1,0,1>: 0.1370100986358818\n", "}, 'global_perf': 0.11111111111111113}\n" ] } ], "source": [ "experiment.min_detected_photons_filter(3)\n", "\n", "# Since we use a \"custom\" (understand not a BasicState) input state,\n", "# we have to add the heralds from the post-processed cnot manually\n", "input_state *= pcvl.BasicState([0, 0])\n", "\n", "experiment.with_input(input_state)\n", "\n", "p = pcvl.Processor(\"SLOS\", experiment)\n", "res = p.probs()\n", "print(res)" ] }, { "cell_type": "markdown", "id": "78778a2ac0806cd1", "metadata": {}, "source": [ "Notice that when using feed-forward, the performance indicators are replaced by a single indicator \"global_perf\", which represents the probability that an output state checks all requirements. In our case, this corresponds to the CNOT gate performance: $1 / 9 \\approx 0.111$.\n", "\n", "For the results, we don't need to know what was measured by Alice, so we need to squash the resulting probabilities to keep only the two last modes." ] }, { "cell_type": "code", "execution_count": 12, "id": "3e6a8afe9b080f15", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", "\t|1,0>: 0.4519596054564726\n", "\t|0,1>: 0.5480403945435272\n", "}\n" ] } ], "source": [ "print(squash_results(res[\"results\"], 4))" ] }, { "cell_type": "markdown", "id": "e656019381c106b2", "metadata": {}, "source": [ "We can now apply our tomography process to check that Bob's qubit is now the initial qubit that Alice wanted to transmit." ] }, { "cell_type": "code", "execution_count": 13, "id": "beea8ac653d00512", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.672*|1,0>+(-0.112-0.732I)*|0,1>\n" ] } ], "source": [ "print(tomography(experiment, 4))" ] }, { "cell_type": "markdown", "id": "6ca68df83bec38d0", "metadata": {}, "source": [ "Tadaaaa! We get the state that we wanted to transmit. Pretty to cool to teleport state in photonics right?" ] }, { "cell_type": "markdown", "id": "71027fa0c349b9d1", "metadata": {}, "source": [ "## References\n", "\n", "> [1] C. H. Bennett, G. Brassard, C. Crépeau, R. Jozsa, A. Peres and W. K. Wootters, “Teleporting an unknown quantum state via dual classical and Einstein-Podolsky-Rosen channels”, [Phys. Rev. Lett.](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.70.1895) **70**, 1895 (1993)." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Reinforcement_learning.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "b1e0bbf3", "metadata": {}, "source": [ "# Reinforcement learning" ] }, { "cell_type": "markdown", "id": "58b57fee", "metadata": {}, "source": [ "## Goal and perspectives\n", "\n", "This tutorial is mainly adapted from this article https://arxiv.org/pdf/2103.06294.pdf and inspired by the work done during the 2022 LOQCathon." ] }, { "attachments": {}, "cell_type": "markdown", "id": "8078725c", "metadata": {}, "source": [ "### Reinforcement learning\n", "\n", "Reinforcement learning is a machine learning framework where an agent tries to find the right actions to perform by interacting with an environment. This is modelled by an agent who is taking actions and receiving percepts from the environment which can be used to choose the next actions. At the end of an epoch (or a series of epochs), the environment rewards (or not) the agent according to the string of actions taken. From the reward, the agent learns and adapts their strategy for the next epochs.\n", "\n", "![reinforcement_learning.png](../_static/img/reinforcement-learning_reinforcement_learning.png)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "5a7f63da", "metadata": {}, "source": [ "In Quantum Reinforcement learning, the exchanged actions, percepts and rewards are now quantum states being exchanged between the environment and the agent. In their paper, the authors claimed to have found a quantum advantage in the time for training by making use of Grover's amplification method to reach good actions quicker, and the goal of this tutorial is to reproduce the main results of their paper:\n", "\n", "![results_paper.png](../_static/img/reinforcement-learning_results_paper.png)\n" ] }, { "cell_type": "markdown", "id": "4f74c3b5", "metadata": {}, "source": [ "## Imports of packages and configuration of display" ] }, { "cell_type": "code", "execution_count": 1, "id": "7085c79d", "metadata": {}, "outputs": [], "source": [ "from __future__ import annotations\n", "\n", "import math\n", "\n", "from ipywidgets import FloatProgress\n", "from IPython.display import display\n", "\n", "import perceval as pcvl\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from perceval.components.unitary_components import Unitary, BS, PS\n", "from perceval.rendering import DisplayConfig, SymbSkin\n", "\n", "DisplayConfig.select_skin(SymbSkin)" ] }, { "cell_type": "markdown", "id": "edc77d36", "metadata": {}, "source": [ "## Grover's algorithm" ] }, { "cell_type": "markdown", "id": "c50e3403", "metadata": {}, "source": [ "We will now implement Grover's algorithm, firstly from a high-level picture (using unitaries) and then photonically (using Mach-Zehnder interferometers) in the same way that was done in the article." ] }, { "cell_type": "markdown", "id": "e89e9be7", "metadata": {}, "source": [ "### Grover's algorithm in high-level picture" ] }, { "cell_type": "code", "execution_count": 2, "id": "83faf254", "metadata": {}, "outputs": [], "source": [ "# Let's create a function that generates our circuit, given an angle xi\n", "\n", "def grover_circuit_unitaries(xi:float) -> pcvl.Circuit:\n", " # Unitary to go from |00> to cos(xi)|10> + sin(xi)|01>\n", " unitary_p = pcvl.Matrix(\n", " np.array([[math.cos(xi), -math.sin(xi)], [math.sin(xi), math.cos(xi)]])\n", " )\n", " # Unitary for Hadamard gate\n", " unitary_hadamard = pcvl.Matrix(1 / math.sqrt(2) * np.array([[1, 1], [1, -1]]))\n", " # Unitary for environment interaction, that switches the phase of the good state\n", " unitary_env = pcvl.Matrix(np.array([[0, -1], [-1, 0]]))\n", " # Unitary of the reflection\n", " unitary_reflection = pcvl.Matrix(\n", " np.array(\n", " [\n", " [math.cos(2 * xi), math.sin(2 * xi)],\n", " [math.sin(2 * xi), -math.cos(2 * xi)],\n", " ]\n", " )\n", " )\n", " \n", " # We can now assemble our circuit\n", " hadamard_component = Unitary(unitary_hadamard, \"H\")\n", " circuit = pcvl.Circuit(4) // (1, Unitary(unitary_p, \"P\")) // (0, hadamard_component) // (2, hadamard_component) // (2, Unitary(unitary_env, \"env\"))\\\n", " // (0, hadamard_component) // (2, hadamard_component) // (1, Unitary(unitary_reflection, \"reflection\"))\n", " return circuit" ] }, { "cell_type": "code", "execution_count": 3, "id": "94b60295", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "P\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "env\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "reflection\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pcvl.pdisplay(grover_circuit_unitaries(math.pi/3))" ] }, { "cell_type": "markdown", "id": "01ddb6f0", "metadata": {}, "source": [ "One step of amplitude amplification in Grover's algorithm should rotate our state from a $\\xi$ angle to a $3\\xi$ angle from the loosing state to the winning state. Hence we can check the validity of our Grover's amplification algorithm by inputting a photon in the spatial mode 1 and detecting at spatial mode 2. This should follow a $\\sin(3\\xi)^2$ distribution." ] }, { "cell_type": "code", "execution_count": 4, "id": "d1f06163", "metadata": {}, "outputs": [], "source": [ "xis = np.linspace(0, math.pi/2, 100)\n", "results = []\n", "for xi in xis:\n", " circuit = grover_circuit_unitaries(xi)\n", " backend = pcvl.BackendFactory.get_backend()\n", " backend.set_circuit(circuit)\n", " input_state = pcvl.BasicState([0, 1, 0, 0])\n", " backend.set_input_state(input_state)\n", " results.append(backend.probability(pcvl.BasicState([0, 0, 1, 0])))" ] }, { "cell_type": "code", "execution_count": 5, "id": "09fc599f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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hw8f5pdffmHEiBGEh4eTlpbGJ598Qp8+fUhKSqJevXr07t2befPmce+99zJw4EDGjBlj/f5Tp06xcuVK5s2bV+SzH374Yb788kvGjRvHpEmTSE5O5oMPPmDbtm1s2LABd3d3a9p9+/YxevRoHn74YcaPH0/z5s2Lze/+/fvZv38/Dz74IL6+vtd0DIv7jrS0NLp3705ubi6TJk2idu3afPXVV9x666388MMPDB8+nLp169KnTx++++47YmNj7T5z0aJF6PV6RowYAZgHt3369OHkyZM8/PDD3HDDDfz9999MmTKFlJSUIvM5vvjiC/Ly8njooYfw9PSkVq1axV69+c9//sPu3bt5/PHHCQsLIz09nZUrV3Ls2LEik3NtrVy5ksOHDzNu3DhCQkLYvXs3n376Kbt37+aff/4p0iZHjhxJy5Ytee2111i6dCn/+9//qFWrFp988gn9+/fn9ddfZ8GCBTzzzDN06tSJ3r172+3/yiuvoCgKzz33HOnp6cyaNYsBAwawffv2Ul8le/jhh6lfvz6vvvoqkyZNolOnTtStW7fE9HFxcUybNo3u3bszffp0PDw8+Pfff1mzZg033XQTYB6g+/r6MnnyZHx9fVmzZg1Tp04lOzubN998E4AXXniBrKwsTpw4Ye0rrtTGVq1axeDBg2ncuDHTpk3j4sWLvP/++/To0YOtW7cWqZc777yT8PBwZsyYwdatW/nss88IDg7m9ddfL9VxEZXAkffEhOuyzFtZtWqVdvr0ae348ePawoULtdq1a2ve3t7aiRMnNE0z37MHtOeff95u/19++UUDtP/973922++44w5NURTt4MGD1m2ABmibN2+2bjt69Kjm5eWlDR8+3LotNze3SD43btyoAdrXX39dJO8dOnTQCgoKrNvfeOMNDdAWL15s3danTx+tT58+1tfFzbmJjY0tMpelpDk3U6ZM0Tw9PbVz585Zt6Wnp2tubm5abGxskfS28vLyNJPJZLctOTlZ8/T01KZPn263nWLmKZU05+bPP//UAG3BggV22+Pj44tsb9SokQZo8fHxV8yrpmna4sWLNUCbNWuW3XZVVbXTp0/b/bOdw1HSdzz55JMaoP3555/WbTk5OVp4eLgWFhZmPTaffPKJBmi7du2y2z8yMlLr37+/9fXLL7+s1ahRQ9u/f79duueff17T6/XasWPHNE27XOf+/v5aenr6Fct89uxZDdDefPPNqx2eIoprv99++60GaOvXr7dus7S3hx56yLrNaDRqDRo00BRF0V577TW7/Hh7e9u1xbVr12qAVr9+fS07O9u6/bvvvtMA7d1337VuK27ODWDXVi2f9/3339ulK3xeHDhwQNPpdNrw4cOLtGNVVa94HB5++GHNx8dHy8vLs24rac5NcedodHS0FhwcrGVkZFi37dixQ9PpdNqYMWOK5Pn++++3+8zhw4drtWvXLvJdwnHktpSoUAMGDCAoKIiGDRsyatQofH19+fnnn6lfv75dugkTJti9XrZsGXq9nkmTJtltf/rpp9E0jd9//91ue7du3ejQoYP19Q033MBtt93G8uXLrZerbX9tGgwGMjIyaNKkCYGBgWzdurVI3h966CG7KxITJkzAzc2NZcuWXeNRKL0xY8aQn5/PDz/8YN22aNEijEYj99xzzxX39fT0RKczn9Imk4mMjAx8fX1p3rx5seUrre+//56AgAAGDhzImTNnrP86dOiAr68va9eutUsfHh7OoEGDrvq52dnZQNFf1FlZWQQFBdn92759+1W/Y9myZXTu3JmePXtat/n6+vLQQw9x5MgR622i22+/HTc3NxYtWmRNl5iYSFJSEiNHjrQrd69evahZs6ZduQcMGIDJZCpy2/M///kPQUFBVyyzt7c3Hh4erFu3rsgtvauxbb95eXmcOXOGrl27AhRbvw8++KD1/3q9no4dO6JpGg888IB1e2BgIM2bN+fw4cNF9h8zZgx+fn7W13fccQehoaEV1v5/+eUXVFVl6tSp1nZsYXtVyvY45OTkcObMGXr16kVubi579+695u9NSUlh+/btjB07llq1alm3t2nThoEDBxZb3kceecTuda9evcjIyLC2aeF4MrgRFerDDz9k5cqVrF27lqSkJA4fPlzkj5KbmxsNGjSw23b06FHq1atn17kCtGzZ0vq+raZNmxb57mbNmpGbm8vp06cBuHjxIlOnTrXOn6hTpw5BQUGcO3eOrKysIvsX/kxfX19CQ0OvOjfierRo0YJOnTqxYMEC67YFCxbQtWtXmjRpcsV9VVXlnXfeoWnTpnbl27lzZ7HlK60DBw6QlZVFcHBwkUHH+fPnrRNGLcLDw0v1uZa6PX/+vN12X19fVq5cycqVK/nvf/9b7L7FfcfRo0eLvQVWuM3UqVOHG2+8ke+++86aZtGiRbi5uXH77bdbtx04cID4+PgiZR4wYABAmcrt6enJ66+/zu+//07dunXp3bs3b7zxBqmpqVfdNzMzkyeeeIK6devi7e1NUFCQ9TuLq98bbrjB7nVAQABeXl7UqVOnyPbiBlqF27+iKDRp0qTC2v+hQ4fQ6XRERkZeMd3u3bsZPnw4AQEB+Pv7ExQUZB34l6WdW9pFSW3nzJkzXLhwwW574WNbs2ZNgGsesIqKI3NuRIXq3Lmz9WmpkthecahIjz/+OF988QVPPvkk3bp1IyAgAEVRGDVqVJV6smXMmDE88cQTnDhxgvz8fP755x+7Sa4lefXVV3nppZe4//77efnll6lVqxY6nY4nn3zyusqnqirBwcF2Ay5bha9WlHY+RosWLQDzVRNbbm5u1gHEiRMnit33ep+MGjVqFOPGjWP79u1ER0fz3XffceONN9r94VdVlYEDB/Lss88W+xnNmjUrU56efPJJhg4dyi+//MLy5ct56aWXmDFjBmvWrKFdu3Yl7nfnnXfy999/89///pfo6Gh8fX1RVZWYmJhi61ev15dqG1DiJP2q5ty5c/Tp0wd/f3+mT59OREQEXl5ebN26leeee67SzmNnP47VgQxuRJXUqFEjVq1aRU5Ojt3VG8tl58KLgB04cKDIZ+zfvx8fHx/rH98ffviB++67z+7pkLy8PM6dO1dsHg4cOEC/fv2sr8+fP09KSgpDhgwpc7ksrjQhedSoUUyePJlvv/2Wixcv4u7ubne7pCQ//PAD/fr14/PPP7fbfu7cuSK/1q8lTxEREaxatYoePXqU6+PWzZs3p2nTpvzyyy/MmjXruh//b9SoEfv27Suyvbg2M2zYMB5++GHrran9+/czZcoUu/0iIiI4f/68daBVniIiInj66ad5+umnOXDgANHR0bz99tvMnz+/2PRnz55l9erVxMXFMXXqVOv24tp9eSn82ZqmcfDgQdq0aVMh3xcREYGqqiQlJREdHV1smnXr1pGRkcFPP/1kNwHa8vSlrdJO+re0i5LaTp06dZxmaQpxmdyWElXSkCFDMJlMRa5YvPPOOyiKwuDBg+22b9y40W7ewfHjx1m8eDE33XST9VeWXq8v8svq/ffft3uE1Nann36KwWCwvp49ezZGo7HId5dFjRo1ShxU1alTh8GDBzN//nwWLFhATExMqQYnxZXv+++/5+TJk6XOE1AkX3feeScmk4mXX365yD5Go7HEcpTGtGnTOHPmDOPHj7c71hbX8kt4yJAhJCQk2D0uf+HCBT799FPCwsLsbncEBgYyaNAgvvvuOxYuXIiHhwfDhg2z+7w777yTjRs3snz58iLfde7cOYxGY6nzZpGbm0teXp7dtoiICPz8/MjPzy9xP0sbLnw8KnIF3q+//pqcnBzr6x9++IGUlJRyaf/FGTZsGDqdjunTpxe5AmMpd3HHoaCggI8++qjI59WoUaNUt6lCQ0OJjo7mq6++smvLiYmJrFixolx+zIjKJ1duRJU0dOhQ+vXrxwsvvMCRI0do27YtK1asYPHixTz55JNERETYpY+KimLQoEF2j4IDduuZ3HLLLcybN4+AgAAiIyPZuHEjq1atsnss3VZBQQE33ngjd955J/v27eOjjz6iZ8+e3Hrrrdddvg4dOrBq1SpmzpxJvXr1CA8Pp0uXLtb3x4wZwx133AFQ7KCiOLfccgvTp09n3LhxdO/enV27drFgwQIaN25c6jwBTJo0iUGDBqHX6xk1ahR9+vTh4YcfZsaMGWzfvp2bbroJd3d3Dhw4wPfff8+7775rzeu1uuuuu0hMTGTGjBkkJCQwatQowsPDuXDhAomJiXz77bf4+flZ5zRcyfPPP8+3337L4MGDmTRpErVq1eKrr74iOTmZH3/8scitz5EjR3LPPffw0UcfMWjQoCKLzP33v//l119/5ZZbbmHs2LF06NCBCxcusGvXLn744QeOHDlSqkGnrf3791vbVGRkJG5ubvz888+kpaUxatSoEvfz9/e3zs8xGAzUr1+fFStWFHvForzUqlWLnj17Mm7cONLS0pg1axZNmjRh/PjxFfJ9TZo04YUXXuDll1+mV69e3H777Xh6erJp0ybq1avHjBkz6N69OzVr1uS+++5j0qRJKIrCvHnzih0Ed+jQgUWLFjF58mQ6deqEr68vQ4cOLfa733zzTQYPHky3bt144IEHrI+CF7fOknASjnlIS7i60oYwuO+++7QaNWoU+15OTo721FNPafXq1dPc3d21pk2bam+++abdY6Gadvmx5vnz52tNmzbVPD09tXbt2hVZ8v7s2bPauHHjtDp16mi+vr7aoEGDtL1792qNGjWyexTWkvc//vhDe+ihh7SaNWtqvr6+2t133233qKim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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(xis, results, \"-o\", label=\"Simulation\")\n", "plt.plot(xis, np.sin((3*xis)) ** 2, \"--\", label=\"Theoretical\")\n", "plt.title(\"Probability after Grover's amplification\")\n", "plt.xlabel(\"$\\\\xi$\")\n", "plt.ylabel(\"Probability\")\n", "plt.grid()\n", "plt.legend()" ] }, { "cell_type": "markdown", "id": "4ebd4183", "metadata": {}, "source": [ "### Designing the circuit with Mach-Zehnder interferometers" ] }, { "attachments": {}, "cell_type": "markdown", "id": "a46700fb", "metadata": {}, "source": [ "In the article they have used Mach-Zehnder interferometers (see below) to realize all the circuits.\n", "\n", "![mzi.png](../_static/img/reinforcement-learning_mzi.png)\n", "\n", "\n", "We want to perform the following unitary transformation (equation A.19 in the paper)\n", "\n", "$$U_{\\theta,\\varphi} = \\begin{pmatrix}e^{i\\varphi}\\sin\\left(\\frac{\\theta}{2}\\right) & e^{i\\varphi}\\cos\\left(\\frac{\\theta}{2}\\right)\\\\ \\cos\\left(\\frac{\\theta}{2}\\right) & -\\sin\\left(\\frac{\\theta}{2}\\right) \\end{pmatrix}$$\n", "\n", "However taking the MZI as shown and using the Rx convention for beam splitters yields the following matrix (see documentation [here](https://perceval.quandela.net/docs/components.html#beam-splitter)).\n", "\n", "\n", "$$ie^{i\\frac{\\theta}{2}}\\begin{pmatrix}e^{i\\varphi}\\sin\\left(\\frac{\\theta}{2}\\right) & e^{i\\varphi}\\cos\\left(\\frac{\\theta}{2}\\right)\\\\ \\cos\\left(\\frac{\\theta}{2}\\right) & -\\sin\\left(\\frac{\\theta}{2}\\right) \\end{pmatrix}$$\n", "\n", "To remove this global phase effect, we use phase shifters with angle $\\theta_2$ to be $-\\frac \\pi 2 - \\frac \\theta 2$:" ] }, { "cell_type": "code", "execution_count": 6, "id": "9cc0419f", "metadata": {}, "outputs": [], "source": [ "def mzi(name:str, theta:float | pcvl.Parameter, phi:float | pcvl.Parameter, theta_2:float | pcvl.Parameter) -> pcvl.Circuit:\n", " # For the mzi to be in the right shape:\n", " # theta_2 should be set to '- pi/2 - theta/2'\n", " # however we cannot pass a symbolic expression to the input of PS\n", " # so we need to define a third angle theta_2 that we will set to '- pi/2 - theta/2' later on\n", " return (\n", " pcvl.Circuit(2, name=name)\n", " .add(0, BS())\n", " .add(0, PS(theta))\n", " .add(0, BS())\n", " .add(0, PS(phi))\n", " .add(0, PS(theta_2))\n", " .add(1, PS(theta_2))\n", " )\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "79a8d2a1", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=theta_t\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_t\n", "\n", "\n", "Φ=theta_2\n", "\n", "\n", "Φ=theta_2\n", "\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "theta_t = pcvl.P(\"theta_t\")\n", "phi_t = pcvl.P(\"phi_t\")\n", "theta_2 = pcvl.P(\"theta_2\")\n", "\n", "pcvl.pdisplay(mzi(\"test\", theta_t, phi_t, theta_2))" ] }, { "cell_type": "markdown", "id": "8f282446", "metadata": {}, "source": [ "#### Implementing the gates with MZI" ] }, { "cell_type": "markdown", "id": "1227b4b9", "metadata": {}, "source": [ "##### Hadamard\n", "\n", "For the Hadamard, we want $\\theta$ and $\\varphi$ such that\n", "\n", "$$\\begin{pmatrix}e^{i\\varphi}\\sin\\left(\\frac{\\theta}{2}\\right) & e^{i\\varphi}\\cos\\left(\\frac{\\theta}{2}\\right)\\\\ \\cos\\left(\\frac{\\theta}{2}\\right) & -\\sin\\left(\\frac{\\theta}{2}\\right) \\end{pmatrix} = \\frac{1}{\\sqrt{2}}\\begin{pmatrix}1 & 1\\\\ 1 & -1 \\end{pmatrix}$$\n", "\n", "so we set $\\theta = \\frac{\\pi}{2}$ and $\\varphi = 0$." ] }, { "cell_type": "code", "execution_count": 8, "id": "5edc592f", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}0.707106781186547 - 1.11022302462516 \\cdot 10^{-16} i & \\frac{i \\left(-3.33066907387547 \\cdot 10^{-16} - 1.4142135623731 i\\right)}{2}\\\\\\frac{i \\left(-3.33066907387547 \\cdot 10^{-16} - 1.4142135623731 i\\right)}{2} & -0.707106781186547 + 1.11022302462516 \\cdot 10^{-16} i\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "hadamard = mzi(\"H\", math.pi/2, 0, -math.pi/2 - math.pi/4)\n", "\n", "pcvl.pdisplay(hadamard.U)" ] }, { "cell_type": "markdown", "id": "a5ce938e", "metadata": {}, "source": [ "##### Environment\n", "For the environment, we want a matrix of the form\n", "\n", "$$\\begin{pmatrix}e^{i\\varphi}\\sin\\left(\\frac{\\theta}{2}\\right) & e^{i\\varphi}\\cos\\left(\\frac{\\theta}{2}\\right)\\\\ \\cos\\left(\\frac{\\theta}{2}\\right) & -\\sin\\left(\\frac{\\theta}{2}\\right) \\end{pmatrix} = \\begin{pmatrix}0 & -1\\\\ -1 & 0 \\end{pmatrix}$$\n", "\n", "which gives $\\theta = -2\\pi$ and $\\varphi=0$." ] }, { "cell_type": "code", "execution_count": 9, "id": "5a76c917", "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{matrix}1.22464679914735 \\cdot 10^{-16} & \\frac{i \\left(3.67394039744206 \\cdot 10^{-16} + 2.0 i\\right)}{2}\\\\\\frac{i \\left(3.67394039744206 \\cdot 10^{-16} + 2.0 i\\right)}{2} & -1.22464679914735 \\cdot 10^{-16}\\end{matrix}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "env = mzi(\"U_E\", -2 * math.pi, 0, -math.pi/2 + math.pi)\n", "\n", "pcvl.pdisplay(env.U)" ] }, { "cell_type": "markdown", "id": "5c740108", "metadata": {}, "source": [ "##### Preparation of state\n", "\n", "For the preparation of the state, we want a matrix of the form\n", "\n", "\n", "$$\\begin{pmatrix}e^{i\\varphi}\\sin\\left(\\frac{\\theta}{2}\\right) & e^{i\\varphi}\\cos\\left(\\frac{\\theta}{2}\\right)\\\\ \\cos\\left(\\frac{\\theta}{2}\\right) & -\\sin\\left(\\frac{\\theta}{2}\\right) \\end{pmatrix} = \\begin{pmatrix}\\cos(\\xi) & -\\sin(\\xi)\\\\ \\sin(\\xi) & \\cos(\\xi) \\end{pmatrix}$$\n", "\n", "which gives $\\theta = \\pi - 2\\xi$ and $\\varphi=0$.\n" ] }, { "cell_type": "code", "execution_count": 10, "id": "30243876", "metadata": {}, "outputs": [], "source": [ "theta_prep = pcvl.P(\"theta_prep\") # We will set it to pi - 2*xi later\n", "theta2_prep = pcvl.P(\"theta2_prep\") # We will set it to -pi/2 - pi/2 + xi = -pi + xi later as we cannot pass symbolic expression to the function mzi\n", "state_prep = mzi(\"U_p\", theta_prep, 0, theta2_prep)" ] }, { "cell_type": "markdown", "id": "6905688f", "metadata": {}, "source": [ "##### Reflection\n", "\n", "For the reflection, we want a matrix of the form \n", "\n", "$$\\begin{pmatrix}e^{i\\varphi}\\sin\\left(\\frac{\\theta}{2}\\right) & e^{i\\varphi}\\cos\\left(\\frac{\\theta}{2}\\right)\\\\ \\cos\\left(\\frac{\\theta}{2}\\right) & -\\sin\\left(\\frac{\\theta}{2}\\right) \\end{pmatrix} = \\begin{pmatrix}\\cos(2\\xi) & \\sin(2\\xi)\\\\ \\sin(2\\xi) & -\\cos(2\\xi) \\end{pmatrix}$$\n", "\n", "which gives $\\theta = \\pi - 4\\xi$ and $\\varphi=0$" ] }, { "cell_type": "code", "execution_count": 11, "id": "a640d216", "metadata": {}, "outputs": [], "source": [ "theta_ref = pcvl.P(\"theta_ref\") # We will set it to pi - 4*xi later\n", "theta2_ref = pcvl.P(\"theta2_ref\") # We will set it to -pi/2 - pi/2 + 2xi = -pi + 2xi later as we cannot pass symbolic expression to the function mzi\n", "ref = mzi(\"U_ref\", theta_ref, 0, theta2_ref)" ] }, { "cell_type": "markdown", "id": "f0e532b8", "metadata": {}, "source": [ "### Grover's algorithm with MZI" ] }, { "cell_type": "markdown", "id": "3acb6e44", "metadata": {}, "source": [ "We now implement again Grover's algorithm with MZI implementation as a sanity check for the definitions of the gates we chose." ] }, { "cell_type": "code", "execution_count": 12, "id": "5029c0f7", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "U_P\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=theta_prep\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=theta2_prep\n", "\n", "\n", "Φ=theta2_prep\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "U_E\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2*pi\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "H\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "H\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "U_REF\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=theta_ref\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=theta2_ref\n", "\n", "\n", "Φ=theta2_ref\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "circuit = pcvl.Circuit(4)\n", "circuit.add(1, state_prep).add(0, hadamard).add(2, hadamard).add(2, env).add(0, hadamard).add(2, hadamard).add(1, ref)\n", "\n", "pcvl.pdisplay(circuit, recursive=True)" ] }, { "cell_type": "code", "execution_count": 13, "id": "3d002e05", "metadata": {}, "outputs": [], "source": [ "results_mzis = []\n", "\n", "for xi in xis:\n", " # Update values in the circuit\n", " theta1 = math.pi - 2*xi #set the angle as explained above in 'Preparation of state'\n", " theta_prep.set_value(theta1)\n", " theta2_prep.set_value(-math.pi/2 - theta1/2)\n", " \n", " theta2 = math.pi - 4*xi #set the angle as explained above in 'Reflection'\n", " theta_ref.set_value(theta2)\n", " theta2_ref.set_value(-math.pi/2 - theta2/2)\n", " \n", " backend = pcvl.BackendFactory.get_backend()\n", " backend.set_circuit(circuit)\n", " input_state = pcvl.BasicState([0, 1, 0, 0])\n", " backend.set_input_state(input_state)\n", " results_mzis.append(backend.probability(pcvl.BasicState([0, 0, 1, 0])))" ] }, { "cell_type": "code", "execution_count": 14, "id": "68692596", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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kyZNiI+P55NPPqFv377s3buXevXq0adPH77++mvuueceBg4cyJgxYxyff/bsWWJiYvj6668L7fvBBx9k/vz5jB8/nokTJ3Ls2DE+/PBDYmNjWb9+PXq93pH2wIED3HXXXTz44INMmDCBVq1aFZnfgwcPcvDgQe6//358fHzKdAyL+oz4+Hh69uxJVlYWEydOJCgoiAULFnDTTTexZMkSbr31VkJDQ+nbty8//PAD06ZNc9nn4sWL0Wq1DB8+HLAFt3379uXMmTM8+OCDNGzYkA0bNjB58mTi4uIKjef46quvyMnJ4YEHHsBgMFCnTp0ie29uv/129uzZw+OPP07jxo05f/48MTExnDx5stDgXGcxMTEcPXqU8ePHExYWxp49e/j000/Zs2cP//77b6E6OXLkSK666ipef/11li1bxiuvvEKdOnX45JNP6N+/P2+88QaLFi3imWee4ZprrqFPnz4u73/11VdRFIX/+7//4/z588yePZsBAwawY8eOUveSPfjgg9SvX5/XXnuNiRMncs011xAaGlps+hkzZjB9+nR69uzJzJkz8fDwYNOmTfz111/ccMMNgC1A9/HxYdKkSfj4+PDXX38xdepU0tLSeOuttwB48cUXSU1N5fTp0462oqQ6tnLlSgYNGkTTpk2ZPn062dnZfPDBB/Tq1Yvt27cXOi8jRoygSZMmzJo1i+3bt/P5559Tt25d3njjjVIdF3EFuPOemKi57ONWVq5cqSYkJKinTp1Sv//+ezUoKEg1Go3q6dOnVVW13bMH1Oeff97l/UuXLlUB9ZVXXnHZfscdd6iKoqiHDx92bANUQN26datj24kTJ1RPT0/11ltvdWzLysoqlM+NGzeqgLpw4cJCee/SpYual5fn2P7mm2+qgPrLL784tvXt21ft27ev4++ixtxMmzat0FiW4sbcTJ48WTUYDGpKSopj2/nz51WdTqdOmzatUHpnOTk5qsVicdl27Ngx1WAwqDNnznTZThHjlIobc7N27VoVUBctWuSyfcWKFYW2N2rUSAXUFStWlJhXVVXVX375RQXU2bNnu2y3Wq1qQkKCyz/nMRzFfcaTTz6pAuratWsd29LT09UmTZqojRs3dhybTz75RAXUXbt2uby/TZs2av/+/R1/v/zyy6q3t7d68OBBl3TPP/+8qtVq1ZMnT6qqevGc+/n5qefPny+xzMnJySqgvvXWW5c6PIUUVX+/++47FVD/+ecfxzZ7fXvggQcc28xms9qgQQNVURT19ddfd8mP0Wh0qYt///23Cqj169dX09LSHNt/+OEHFVDff/99x7aixtwALnXVvr8ff/zRJV3B6+LQoUOqRqNRb7311kL12Gq1lngcHnzwQdXLy0vNyclxbCtuzE1R12jHjh3VunXrqklJSY5tO3fuVDUajTpmzJhCeb733ntd9nnrrbeqQUFBhT5LuI/clhKVasCAAYSEhBAREcGdd96Jj48P//vf/6hfv75Luocfftjl7+XLl6PVapk4caLL9qeffhpVVfnjjz9ctkdGRtKlSxfH3w0bNuTmm28mOjra0V3t/GvTZDKRlJRE8+bNCQgIYPv27YXy/sADD7j0SDz88MPodDqWL19exqNQemPGjCE3N5clS5Y4ti1evBiz2czdd99d4nsNBgMaje2StlgsJCUl4ePjQ6tWrYosX2n9+OOP+Pv7M3DgQBITEx3/unTpgo+PD3///bdL+iZNmhAVFXXJ/aalpQGFf1GnpqYSEhLi8m/Hjh2X/Izly5fTrVs3rr32Wsc2Hx8fHnjgAY4fP+64TXTbbbeh0+lYvHixI93u3bvZu3cvI0eOdCl37969CQwMdCn3gAEDsFgshW573n777YSEhJRYZqPRiIeHB6tXry50S+9SnOtvTk4OiYmJ9OjRA6DI83v//fc7/l+r1dK1a1dUVeW+++5zbA8ICKBVq1YcPXq00PvHjBmDr6+v4+877riD8PDwSqv/S5cuxWq1MnXqVEc9tnPulXI+Dunp6SQmJtK7d2+ysrLYv39/mT83Li6OHTt2MG7cOOrUqePYfvXVVzNw4MAiy/vQQw+5/N27d2+SkpIcdVq4nwQ3olJ99NFHxMTE8Pfff7N3716OHj1a6EtJp9PRoEEDl20nTpygXr16Lo0rwFVXXeV43VmLFi0KfXbLli3JysoiISEBgOzsbKZOneoYPxEcHExISAgpKSmkpqYWen/Bffr4+BAeHn7JsRGXo3Xr1lxzzTUsWrTIsW3RokX06NGD5s2bl/heq9XKe++9R4sWLVzK999//xVZvtI6dOgQqamp1K1bt1DQkZGR4RgwatekSZNS7dd+bjMyMly2+/j4EBMTQ0xMDM8++2yR7y3qM06cOFHkLbCCdSY4OJjrr7+eH374wZFm8eLF6HQ6brvtNse2Q4cOsWLFikJlHjBgAEC5ym0wGHjjjTf4448/CA0NpU+fPrz55pucO3fuku+9cOECTzzxBKGhoRiNRkJCQhyfWdT5bdiwocvf/v7+eHp6EhwcXGh7UYFWwfqvKArNmzevtPp/5MgRNBoNbdq0KTHdnj17uPXWW/H398fPz4+QkBBH4F+eem6vF8XVncTERDIzM122Fzy2gYGBAGUOWEXlkTE3olJ169bN8bRUcZx7HCrT448/zldffcWTTz5JZGQk/v7+KIrCnXfeWaWebBkzZgxPPPEEp0+fJjc3l3///ddlkGtxXnvtNaZMmcK9997Lyy+/TJ06ddBoNDz55JOXVT6r1UrdunVdAi5nBXsrSjseo3Xr1oCt18SZTqdzBBCnT58u8r2X+2TUnXfeyfjx49mxYwcdO3bkhx9+4Prrr3f54rdarQwcOJDnnnuuyH20bNmyXHl68sknGTZsGEuXLiU6OpopU6Ywa9Ys/vrrLzp16lTs+0aMGMGGDRt49tln6dixIz4+PlitVm688cYiz69Wqy3VNqDYQfpVTUpKCn379sXPz4+ZM2fSrFkzPD092b59O//3f/93xa7j6n4cawMJbkSV1KhRI1auXEl6erpL742927ngJGCHDh0qtI+DBw/i5eXl+PJdsmQJY8eOdXk6JCcnh5SUlCLzcOjQIa677jrH3xkZGcTFxTF48OByl8uupAHJd955J5MmTeK7774jOzsbvV7vcrukOEuWLOG6667jiy++cNmekpJS6Nd6WfLUrFkzVq5cSa9evSr0cetWrVrRokULli5dyuzZsy/78f9GjRpx4MCBQtuLqjO33HILDz74oOPW1MGDB5k8ebLL+5o1a0ZGRoYj0KpIzZo14+mnn+bpp5/m0KFDdOzYkXfeeYdvvvmmyPTJycmsWrWKGTNmMHXqVMf2oup9RSm4b1VVOXz4MFdffXWlfF6zZs2wWq3s3buXjh07Fplm9erVJCUl8fPPP7sMgLY/femstIP+7fWiuLoTHBxcbaamEBfJbSlRJQ0ePBiLxVKox+K9995DURQGDRrksn3jxo0u4w5OnTrFL7/8wg033OD4laXVagv9svrggw9cHiF19umnn2IymRx/z5s3D7PZXOizy8Pb27vYoCo4OJhBgwbxzTffsGjRIm688cZSBSdFle/HH3/kzJkzpc4TUChfI0aMwGKx8PLLLxd6j9lsLrYcpTF9+nQSExOZMGGCy7G2K8sv4cGDB7N582aXx+UzMzP59NNPady4scvtjoCAAKKiovjhhx/4/vvv8fDw4JZbbnHZ34gRI9i4cSPR0dGFPislJQWz2VzqvNllZWWRk5Pjsq1Zs2b4+vqSm5tb7Pvsdbjg8ajMGXgXLlxIenq64+8lS5YQFxdXIfW/KLfccgsajYaZM2cW6oGxl7uo45CXl8fcuXML7c/b27tUt6nCw8Pp2LEjCxYscKnLu3fv5s8//6yQHzPiypOeG1ElDRs2jOuuu44XX3yR48eP06FDB/78809++eUXnnzySZo1a+aSvl27dkRFRbk8Cg64zGcydOhQvv76a/z9/WnTpg0bN25k5cqVLo+lO8vLy+P6669nxIgRHDhwgLlz53Lttddy0003XXb5unTpwsqVK3n33XepV68eTZo0oXv37o7Xx4wZwx133AFQZFBRlKFDhzJz5kzGjx9Pz5492bVrF4sWLaJp06alzhPAxIkTiYqKQqvVcuedd9K3b18efPBBZs2axY4dO7jhhhvQ6/UcOnSIH3/8kffff9+R17IaNWoUu3fvZtasWWzevJk777yTJk2akJmZye7du/nuu+/w9fV1jGkoyfPPP893333HoEGDmDhxInXq1GHBggUcO3aMn376qdCtz5EjR3L33Xczd+5coqKiCk0y9+yzz/Lrr78ydOhQxo0bR5cuXcjMzGTXrl0sWbKE48ePlyrodHbw4EFHnWrTpg06nY7//e9/xMfHc+eddxb7Pj8/P8f4HJPJRP369fnzzz+L7LGoKHXq1OHaa69l/PjxxMfHM3v2bJo3b86ECRMq5fOaN2/Oiy++yMsvv0zv3r257bbbMBgMbNmyhXr16jFr1ix69uxJYGAgY8eOZeLEiSiKwtdff11kENylSxcWL17MpEmTuOaaa/Dx8WHYsGFFfvZbb73FoEGDiIyM5L777nM8Cl7UPEuimnDPQ1qipivtEgZjx45Vvb29i3wtPT1dfeqpp9R69eqper1ebdGihfrWW2+5PBaqqhcfa/7mm2/UFi1aqAaDQe3UqVOhKe+Tk5PV8ePHq8HBwaqPj48aFRWl7t+/X23UqJHLo7D2vK9Zs0Z94IEH1MDAQNXHx0cdPXq0y6Oiqlr+R8H379+v9unTRzUajSpQ6LHw3NxcNTAwUPX39y/1FPU5OTnq008/rYaHh6tGo1Ht1auXunHjxkJ5dD5mzsxms/r444+rISEhqqIohfL86aefql26dFGNRqPq6+urtm/fXn3uuefUs2fPOtI0atRIHTJkSKny62z16tXqHXfcoYaHh6t6vV718/NTu3btqk6bNk2Ni4tzSVvSZxw5ckS944471ICAANXT01Pt1q2b+vvvvxeZNi0tzXH8v/nmmyLTpKenq5MnT1abN2+uenh4qMHBwWrPnj3Vt99+2zFNgP2cl+bx7sTERPXRRx9VW7durXp7e6v+/v5q9+7d1R9++OGS7z19+rR66623qgEBAaq/v786fPhw9ezZs4UevbbXt4SEBJf3F3et9e3bV23btq3jb/uj29999506efJktW7duqrRaFSHDBminjhxotA+K+pRcLsvv/xS7dSpk2owGNTAwEC1b9++akxMjOP19evXqz169FCNRqNar1499bnnnlOjo6MLLXORkZGhjho1Sg0ICFABRz6LukZVVVVXrlyp9urVSzUajaqfn586bNgwde/evUXmueCxtbcZx44dK1Qe4R6KqsoIKCGqGrPZTL169Rg2bFihMTRCVKbVq1dz3XXX8eOPP5a7R04Id5MxN0JUQUuXLiUhIcExg7AQQojSkzE3QlQhmzZt4r///uPll1+mU6dOjnV5hBBClJ703AhRhcybN4+HH36YunXrsnDhQndnRwghqiUZcyOEEEKIGkV6boQQQghRo0hwI4QQQogapdYNKLZarZw9exZfX99ST88thBBCCPdSVZX09HTq1at3yfUIa11wc/bsWSIiItydDSGEEEKUw6lTp2jQoEGJaWpdcGNfhPHUqVP4+flV6L5NJhN//vmnY3r6mqg2lBFqRzmljDVHbShnbSgj1I5ylreMaWlpREREuCymXJxaF9zYb0X5+flVSnDj5eWFn59fja6UNb2MUDvKKWWsOWpDOWtDGaF2lPNyy1iaISUyoFgIIYQQNYoEN0IIIYSoUSS4EUIIIUSNIsGNEEIIIWoUCW6EEEIIUaNIcCOEEEKIGkWCGyGEEELUKBLcCCGEEKJGkeBGCCGEEDVKrZuhWIjqxGI2s39TNNnJZzAG1qd19yi0OrlshRBVU1Vps9zaSv7zzz+89dZbbNu2jbi4OP73v/9xyy23lPie1atXM2nSJPbs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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(xis, results, \"-o\", label=\"Simulation (Unitaries)\")\n", "plt.plot(xis, results_mzis, \"-o\", label=\"Simulation (MZIs)\")\n", "plt.plot(xis, np.sin((3*xis)) ** 2, \"--\", label=\"Theoretical\")\n", "plt.title(\"Probability after Grover's amplification\")\n", "plt.xlabel(\"$\\\\xi$\")\n", "plt.ylabel(\"Probability\")\n", "plt.grid()\n", "plt.legend()" ] }, { "cell_type": "markdown", "id": "f746dd6f", "metadata": {}, "source": [ "## Reproducing the results of the paper" ] }, { "cell_type": "markdown", "id": "630086db", "metadata": {}, "source": [ "### Classical circuit" ] }, { "attachments": {}, "cell_type": "markdown", "id": "8e07dd70", "metadata": {}, "source": [ "The classical circuit emulates the agent choosing a good action with probability $\\varepsilon = \\sin^2(\\xi)$. To this end, the operation $U_p |0_A0_R\\rangle = \\cos(\\xi)|0_A0_R\\rangle + \\sin(\\xi)|1_A0_R\\rangle$ is implemented putting the action state in a superposition with the corresponding amplitude. Then the interaction with the environment will make the second qubit switch only if the first qubit is in the state $|1_A\\rangle$, hence putting the reward qubit in the $|1_R\\rangle$ state.\n", "\n", "![classical_circuit.png](../_static/img/reinforcement-learning_classical_circuit.png)\n", "\n", "The detector D1, corresponding to $|0_A0_R\\rangle$, will click with probability $\\cos^2(\\xi) = 1-\\varepsilon$ and corresponds to no reward, whereas detector D2, corresponding to $|1_A1_R\\rangle$, will click with probability $\\sin^2(\\xi) = \\varepsilon$ and correspond to a rewarded action.\n", "\n", "Both operations $U_p$ and $U_e$ were already implemented as part of the Grover's algorithm." ] }, { "cell_type": "code", "execution_count": 15, "id": "4fe0e455", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "U_P\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "\n", "U_E\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2*pi\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "classical_circuit = pcvl.Circuit(4)\n", "classical_circuit.add(1, state_prep).add(2, env) # circuit for classical strategy\n", "pcvl.pdisplay(classical_circuit, recursive=True)" ] }, { "cell_type": "markdown", "id": "d288dabc", "metadata": {}, "source": [ "### Quantum circuit" ] }, { "attachments": {}, "cell_type": "markdown", "id": "6fad7238", "metadata": {}, "source": [ "The quantum circuit performs a Grover's amplification step in order to get the state closer to a rewarded state. However, during this step, no reward is ever perceived and a classical round is still needed afterward to check if the state is rewarded or not.\n", "\n", "![quantum_circuit.png](../_static/img/reinforcement-learning_quantum_circuit.png)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "c3f95a3e", "metadata": {}, "source": [ "We will here, directly append the classical circuit (without state preparation) to the quantum circuit, so that the whole operation is done with one circuits. Note that we will count this as being 2 epochs to have a fair comparison with the classical strategy.\n", "\n", "![classical_quantum.png](../_static/img/reinforcement-learning_classical_quantum.png)" ] }, { "cell_type": "code", "execution_count": 16, "id": "c1a8c795", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "U_P\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "Φ=3*pi/2\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "U_E\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2*pi\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "H\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "H\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "Φ=5*pi/4\n", "\n", "\n", "U_REF\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=pi\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=0\n", "\n", "\n", "\n", "U_E\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2*pi\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "Φ=pi/2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "quantum_circuit = pcvl.Circuit(4)\n", "quantum_circuit.add(1, state_prep).add(0, hadamard).add(2, hadamard).add(2, env).add(0, hadamard).add(2, hadamard).add(1, ref) #circuit for quantum strategy\n", "quantum_circuit.add(2,env) #appending directly the classical round (without preparation) at the end\n", "pcvl.pdisplay(quantum_circuit, recursive=True)" ] }, { "cell_type": "markdown", "id": "367229cf", "metadata": {}, "source": [ "### Simulation" ] }, { "cell_type": "code", "execution_count": 17, "id": "318570c5", "metadata": {}, "outputs": [], "source": [ "# Simulation parameters\n", "N_AGENTS = 100 #number of agents that we simulate and average over\n", "N_EPOCH = 1000\n", "H_0 = 99\n", "H_1 = 1\n", "EPS0 = H_1 / (H_0+H_1)" ] }, { "cell_type": "markdown", "id": "46ff0d04", "metadata": {}, "source": [ "#### Reward function\n", "First we define a reward function that takes as an input a circuit, output a sample state and returns True if it corresponds to a rewarded state (False otherwise)." ] }, { "cell_type": "code", "execution_count": 18, "id": "2369c08f", "metadata": {}, "outputs": [], "source": [ "def get_reward(circuit: pcvl.Circuit) -> bool:\n", " proc = pcvl.Processor(\"SLOS\", circuit)\n", " proc.with_input(pcvl.BasicState([0, 1, 0, 0]))\n", " sampler = pcvl.algorithm.Sampler(proc)\n", " samples = sampler.samples(1)\n", "\n", " # Take a random sample and check if it's the rewarded state or not\n", " return samples[\"results\"][0] == pcvl.BasicState([0, 0, 0, 1])\n" ] }, { "cell_type": "markdown", "id": "ae5b5de3", "metadata": {}, "source": [ "#### Classical strategy" ] }, { "cell_type": "markdown", "id": "2aa85433", "metadata": {}, "source": [ "Below we gather everything defined above to run the purely classical strategy from the article." ] }, { "cell_type": "code", "execution_count": 19, "id": "5cdf619c", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "4569e50c5b80450f9a7e963aad889672", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "eta_classical = []\n", "\n", "# Percentage bar\n", "f = FloatProgress(min=0, max=N_AGENTS)\n", "display(f)\n", "\n", "for agent in range(N_AGENTS): #Loop and average over all agents\n", " f.value = agent\n", " # Initialize initial score\n", " h_0 = H_0\n", " h_1 = H_1\n", " eps = h_1 / (h_0 + h_1)\n", "\n", " # Initialize circuit with initial probability and corresponding angles\n", " xi = math.asin(eps**0.5)\n", " \n", " theta1 = math.pi - 2*xi\n", " theta_prep.set_value(theta1)\n", " theta2_prep.set_value(-math.pi/2 - theta1/2)\n", "\n", " # Arrays of epsilon\n", " eps_array = []\n", " for i in range(N_EPOCH):\n", " if get_reward(classical_circuit): #update policy if output state corresponds to a rewarded state\n", " h_1 = h_1 + 2\n", " eps = h_1 / (h_0 + h_1)\n", " xi = math.asin(eps**0.5)\n", " theta1 = math.pi - 2*xi\n", " theta_prep.set_value(theta1)\n", " theta2_prep.set_value(-math.pi/2 - theta1/2)\n", " eps_array.append(1)\n", " else:\n", " eps_array.append(0)\n", "\n", " eta_classical.append(eps_array)\n", "\n", "eta_classical = np.array(eta_classical)\n", "f.value = N_AGENTS" ] }, { "cell_type": "code", "execution_count": 20, "id": "413721c6", "metadata": {}, "outputs": [ { "data": { "image/png": "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8Y9/RGhoKP7+97+jvr4eTzzxBFJSUhAcHIzOnTvjnXfeMd5n7ty5+Oyzz7B69Wpje5s2bQIAnDlzBmPHjkVkZCRatWqFESNGoKioyHjtjRs3MG3aNERGRqJ169Z46aWXMHHiROPPyeeff47WrVubrV8DgJEjR+LRRx+1azyIyLucKq00Tm4k9aKIolLtISBv5/EJzvXr11FYWIjCwkIADWnghYWFKC5umAxkZ2fjscceM57/5z//GSdPnsSMGTNw+PBhLFy4EF999RWef/55T3RfVUl5NbJX7DObHb+8Yj9Kyqs92zEHPfnkk1i3bh1KSkqMx7777jtUVVUhKytL8ZrCwkLce++96NatG7Zv344tW7bgwQcfVK2TotfrMX/+fOzZswerVq1CUVERJk2aZPx81qxZOHjwIL7//nscOHAAb731FqKjowEA7777Lr799lt89dVXOHLkCL744gskJycr3uf69eu4++67cfbsWXz77bfYs2cPZsyYYXxTcf36ddx///3Iy8tDQUEBhg4digcffND4M2nLjRs3MHLkSNx9993Yu3cvtm/fjj/96U8QBAFZWVl44YUXcMcdd6CkpAQlJSVm4zd37lyMGjUK+/btw+OPPw6DwYB27drh66+/xsGDBzF79my8/PLL+OqrrwAAL774IsaOHYuhQ4ca2+vfvz/0ej0yMzMRFhaGn3/+GVu3bkXLli0xdOhQ49qy119/HV988QU+/fRTbN26FRUVFWaTwIcffhj19fX49ttvjccuXryINWvW4PHHH9c0FkTknVKiQ6GTRZz9BAHJ0b5TA87jIaqdO3di0KBBxq+ltTITJ07E4sWLUVJSYvaLJSUlBWvWrMHzzz+Pd955B+3atcPHH3/sdSniRaVVqrNjV4aqvvvuO7Rs2dL49bBhw/D11183ut3+/fujc+fO+Ne//oUZM2YAAD799FM8/PDDZvcz9cYbb6B3795YuHCh8dgdd9yheg/TX5q33XYb3n33XfTp08e4TUBxcTHS0tLQu3dvGAwGtGrVyrgGpri4GB07dsTvf/97CIKA9u3bq95nyZIluHTpEvLz89GqVSsAMFujlJqaitTUVOPX8+fPx8qVK/Htt99i6tSp1oYJQEOMuLy8HA888ABuv/12AA3lDCQtW7Y0LmaXGz9+PCZPnmx2zHSBfEpKCrZv346vvvoKY8eO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t0dE6jjBISLNamtrcf78ebN/SktLzc4ZP348Fi1ahA0bNmDChAnG42FhYXjxxRfx/PPP47PPPsOJEyewe/duvPfee/jss88c7tOECRMQHR2NESNG4Oeff8apU6ewadMmTJs2Db/99pvxvLq6OjzxxBM4ePAg1q5dizlz5mDq1KnQ6XQIDQ3F008/jb/+9a9Yt24dDh48iKeeegpVVVV44oknAABTp05FRUUFHnnkEezcuRPHjh3Dv/71L5sZXpLvvvsO7777LgoLC3H69Gl8/vnnMBgM6Ny5s8PPTkTqGKIiIs3WrVtnXBMj6dy5Mw4fPmz8esKECfj73/+O9u3bm61pAYD58+cjJiYGOTk5OHnyJCIjI9GrVy+8/PLLDvcpJCQEmzdvxksvvYTRo0fj2rVrSEhIwL333ovw8HDjeffeey86duyIgQMHora2FuPGjcPcuXONn+fm5sJgMODRRx/FtWvX0Lt3b/zwww/GNPjWrVtj48aN+Otf/4q7774bfn5+6Nmzp8UzqomMjMSKFSswd+5c1NTUoGPHjvjyyy9xxx13OPzsRKROEEVR9HQniIhcadKkSSgrK8OqVas83RUichOGqIiIiMjncIJDREREPochKiIiIvI5fINDREREPocTHCIiIvI5nOAQERGRz+EEh4iIiHwOJzhERETkczjBISIiIp/DCQ4RERH5HE5wiIiIyOf8/xsjzDBP2lWGAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(np.mean(eta_classical, axis=0)[::2], \".\", label=\"Fully classical strategy\")\n", "plt.xlabel(\"Even epochs\")\n", "plt.ylabel(\"$\\eta$\")\n", "plt.legend()\n", "plt.grid()" ] }, { "cell_type": "markdown", "id": "9ab3f4c9", "metadata": {}, "source": [ "Here we plot the proportion of agents that gets a reward. In the first epochs, the probability of getting a reward is small and few agents get some. However, once an agent gets a reward, they are more likely to get a reward in the future." ] }, { "cell_type": "markdown", "id": "e4e33516", "metadata": {}, "source": [ "#### Quantum strategy" ] }, { "cell_type": "markdown", "id": "3235182e", "metadata": {}, "source": [ "Below we run the purely quantum strategy." ] }, { "cell_type": "code", "execution_count": 21, "id": "63cdbf11", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "856d305ae94641c388ad55ae0340ed58", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "eta_quantum = []\n", "\n", "# Percentage bar\n", "f = FloatProgress(min=0, max=N_AGENTS)\n", "display(f)\n", "\n", "for agent in range(N_AGENTS):\n", " f.value = agent\n", " # Initialize initial scores\n", " h_0 = H_0\n", " h_1 = H_1\n", " eps = h_1 / (h_0 + h_1)\n", "\n", " # Initialize circuit with initial probability\n", " xi = math.asin(eps**0.5)\n", " \n", " theta1 = math.pi - 2*xi\n", " theta_prep.set_value(theta1)\n", " theta2_prep.set_value(-math.pi/2 - theta1/2)\n", " \n", " theta2 = math.pi - 4*xi\n", " theta_ref.set_value(theta2)\n", " theta2_ref.set_value(-math.pi/2 - theta2/2)\n", "\n", " # Arrays of epsilon\n", " eps_array = []\n", " for i in range(N_EPOCH//2):\n", " if get_reward(quantum_circuit):\n", " h_1 = h_1 + 2\n", " eps = h_1 / (h_0 + h_1)\n", " xi = math.asin(eps**0.5)\n", " \n", " theta1 = math.pi - 2*xi\n", " theta_prep.set_value(theta1)\n", " theta2_prep.set_value(-math.pi/2 - theta1/2)\n", " \n", " theta2 = math.pi - 4*xi\n", " theta_ref.set_value(theta2)\n", " theta2_ref.set_value(-math.pi/2 - theta2/2)\n", " \n", " eps_array.append(0.5)\n", " eps_array.append(0.5)\n", " else:\n", " eps_array.append(0)\n", " eps_array.append(0)\n", "\n", " eta_quantum.append(eps_array)\n", "\n", "eta_quantum = np.array(eta_quantum)\n", "f.value=N_AGENTS" ] }, { "cell_type": "code", "execution_count": 22, "id": "6678e44f", "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjgAAAGwCAYAAACkfh/eAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy80BEi2AAAACXBIWXMAAA9hAAAPYQGoP6dpAABuhElEQVR4nO3deXgUVbo/8G91yE42SCAhJBCQRRTCKhMYUUYUXGZkGc0FZkQG/Y2DuS6MC7iAjGgyDnpd0RnnKuiooBJhriCKQUQHxBDCviMxCM0Slg4kIWnT9fsjVFNdXVVd3em9v5/nwQe6q06dOt2Q1zrvOa8giqIIIiIiojBiCnQHiIiIiLyNAQ4RERGFHQY4REREFHYY4BAREVHYYYBDREREYYcBDhEREYUdBjhEREQUdtoEugOBYLPZcPToUSQlJUEQhEB3h4iIiAwQRRHnzp1Dp06dYDLpP6OJyADn6NGjyMnJCXQ3iIiIyAOHDx9G586ddY+JyAAnKSkJQMsAJScne7Vtq9WKL774AjfccAOio6O92jZdwnH2H461f3Cc/YPj7D++GOva2lrk5OTYf47ricgAR5qWSk5O9kmAk5CQgOTkZP7l8SGOs/9wrP2D4+wfHGf/8eVYG0kvYZIxERERhR0GOERERBR2GOAQERFR2InIHBwiIl+x2Wxoampy+zyr1Yo2bdrgwoULaG5u9kHPCOA4+5OnYx0TE+NyCbgRDHCIiLykqakJhw4dgs1mc/tcURSRmZmJw4cPc38uH+I4+4+nY20ymZCXl4eYmJhWXZ8BDhGRF4iiCLPZjKioKOTk5Lj9f6A2mw3nz59H27ZtvfJ/r6SO4+w/noy1tBGv2WxGbm5uq4JQBjhERF7w888/o76+Hp06dUJCQoLb50tTW3FxcfzB60McZ//xdKwzMjJw9OhR/Pzzz61aXs5Pl4jIC6Qcg9Y+VieKdNLfodbmSDHAISLyIuZ1ELWOt/4OMcAhIiKisMMAh4iIiMIOA5wwZLY0YP3BGpgtDYHuChGFuWuvvRYPPPCA/c9du3bFiy++GLD+EEkY4ISZJeXVGF6yBpPe3IjhJWuwpLw60F0ioiB25513QhAEp18HDhwIdNcCbuHChUhNTQ10N1RVVVVBEARs2bLFK+2tXbsWgiDg7NmzXmkvGDDACSNmSwNmlW6HTWz5s00EHivdwSc5RCHG309hx4wZA7PZ7PArLy/PL9cm3/JkV+1wwQAnjByqqbMHN5JmUURVTX1gOkREbluy6bDfn8LGxsYiMzPT4VdUVBTuvPNOjB071uHYBx54ANdee62hdv/whz/glltucXjNarWiQ4cO+N///V/N8xYuXIjc3FwkJCRg3LhxeP755x2epBjp16pVq/DLX/4SqampaN++PW655RYcPHjQ/r70BKS0tBQjR45EQkIC8vPzsWHDBgAtTzSmTp0Ki8Vif6r11FNPAWhZ5bNs2TKH66empmLhwoUObX/44Ye4+uqrER8fjyFDhmDfvn0oLy/H4MGD0bZtW9x44404efKk5jicOXMGkydPRkZGBuLj49GjRw+8/fbbAGAPQAcMGABBEOz3Lo3NM888g06dOqFXr14AgHfffReDBw9GUlISMjMzMWnSJJw4ccLe35EjRwIA0tLSIAgC7rzzTgAte9kUFxcjLy8P8fHxyM/Px8cff+zQz3//+9/o0aMH4uLiMHLkSCxatMj+NKiurg6pqalO5yxbtgyJiYk4d+6c5v23FgOcMJKXngiTYnVdlCCga7r7m44Rkf8dr23E45/sCJunsHfddRdWrVoFs9lsf+3TTz9FfX09CgsLVc/ZuHEjpk2bhqKiImzZsgUjR47EvHnz3L52XV0dZsyYgU2bNqGsrAwmkwnjxo1zKqPx+OOP46GHHsKWLVvQs2dPTJw4ET///DOGDRuGF198EcnJyfanWg899JBbfZgzZw6eeOIJbN68GW3atMGkSZPwyCOP4KWXXsI333yDAwcOYPbs2ZrnP/nkk9i1axc+++wz7N69G6+//jrS09MBAN9//z0A4Msvv4TZbEZpaan9vLKyMuzduxerV6/Gp59+CqAlsHz66aexdetWLFu2DFVVVfYgJicnB0uXLgUA7N27F2azGS+99BIAoLi4GO+88w7eeOMN7Ny5Ew8++CB+97vf4euvvwYAHDp0CL/97W8xduxYbN26FX/84x/x+OOP2/uSmJiIwsJCe2Amefvtt/Hb3/4WSUlJbo2pO7iTcRjJSolH8fi+eKx0B5pFEVGCgGfHX4mslPhAd42IDKg+06D5FNaXf48//fRTtG3b1v7nG2+8ER999FGr2x02bBh69eqFd999F4888giAlh9st912m8P15F566SWMGTPGfnzPnj2xfv16rFq1yq1rT5gw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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(np.mean(eta_quantum, axis=0)[::2], \".\", label=\"Fully quantum strategy\")\n", "plt.xlabel(\"Even epochs\")\n", "plt.ylabel(\"$\\eta$\")\n", "plt.legend()\n", "plt.grid()" ] }, { "cell_type": "markdown", "id": "fc7ac058", "metadata": {}, "source": [ "Here we see a different behavior. With Grover's amplification, getting a reward is more likely in the first epochs. However, as the probability gets updated, one of Grover's amplification feature will manifest itself: we start to \"overshoot\" or rotate too much and we get past the winning state, and once we got past it, it is not possible to recover the winning state. We converge to a state with zero probability of getting a reward and nothing will get updated. This is why we need to consider an hybrid strategy." ] }, { "cell_type": "markdown", "id": "eee6f816", "metadata": {}, "source": [ "#### Classical-quantum strategy" ] }, { "cell_type": "markdown", "id": "dfe8e73e", "metadata": {}, "source": [ "We want to start with a quantum strategy, and then switch to a classical strategy when e start to overshoot. This happens when the probability of winning in the quantum strategy gets lower than the classical one.\n", "\n", "Due to normalization (and the fact that the quantum strategy takes twice as many epochs than the classical one), this corresponds to solve the equation\n", "\n", "$$\\frac{1}{2}\\sin^2(3\\xi) = \\sin^2(\\xi)$$\n", "\n", "The solution can be found numerically and gives a value of $\\xi = 0.6811$ which corresponds to $\\varepsilon=\\sin^2(\\xi)=0.396$.\n", "\n", "We then choose $Q_L$ to be the value where we switch from quantum to classical and the only requirement is $Q_L<0.396$. To follow [1] we will choose, $Q_L = 0.37$." ] }, { "cell_type": "code", "execution_count": 23, "id": "90a4acc1", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "638a042dc0c84626862f2892b912d2ba", "version_major": 2, "version_minor": 0 }, "text/plain": [ "FloatProgress(value=0.0)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "QL = 0.37\n", "eta_classical_quantum = []\n", "\n", "# Percentage bar\n", "f = FloatProgress(min=0, max=N_AGENTS)\n", "display(f)\n", "\n", "for agent in range(N_AGENTS):\n", " f.value = agent\n", " # Initialize initial scores\n", " h_0 = H_0\n", " h_1 = H_1\n", " eps = h_1 / (h_0 + h_1)\n", "\n", " # Initialize circuit with initial probability\n", " xi = math.asin(eps**0.5)\n", " \n", " theta1 = math.pi - 2*xi\n", " theta_prep.set_value(theta1)\n", " theta2_prep.set_value(-math.pi/2 - theta1/2)\n", " \n", " theta2 = math.pi - 4*xi\n", " theta_ref.set_value(theta2)\n", " theta2_ref.set_value(-math.pi/2 - theta2/2)\n", "\n", " # Arrays of epsilon\n", " eps_array = []\n", " i = 0\n", " while i < N_EPOCH:\n", " if eps < QL:\n", " # Perform a quantum round\n", " if get_reward(quantum_circuit):\n", " h_1 = h_1 + 2\n", " eps = h_1 / (h_0 + h_1)\n", " xi = math.asin(eps**0.5)\n", " theta1 = math.pi - 2*xi\n", " theta_prep.set_value(theta1)\n", " theta2_prep.set_value(-math.pi/2 - theta1/2)\n", " \n", " theta2 = math.pi - 4*xi\n", " theta_ref.set_value(theta2)\n", " theta2_ref.set_value(-math.pi/2 - theta2/2)\n", " eps_array.append(0.5)\n", " eps_array.append(0.5)\n", " else:\n", " eps_array.append(0)\n", " eps_array.append(0)\n", "\n", " # Update epoch by 2\n", " i = i + 2\n", " else:\n", " # Perform a classical round\n", " if get_reward(classical_circuit):\n", " h_1 = h_1 + 2\n", " eps = h_1 / (h_0 + h_1)\n", " xi = math.asin(eps**0.5)\n", " theta1 = math.pi - 2*xi\n", " theta_prep.set_value(theta1)\n", " theta2_prep.set_value(-math.pi/2 - theta1/2)\n", "\n", " eps_array.append(1)\n", " else:\n", " eps_array.append(0)\n", " # Update epoch by 1\n", " i = i + 1\n", "\n", " eta_classical_quantum.append(eps_array)\n", "\n", "eta_classical_quantum = np.array(eta_classical_quantum)\n", "f.value=N_AGENTS" ] }, { "cell_type": "markdown", "id": "20a92f86", "metadata": {}, "source": [ "#### Plots" ] }, { "cell_type": "code", "execution_count": 24, "id": "957d52f1", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.plot(np.mean(eta_classical, axis=0)[::2], \".\", label=\"Fully classical strategy\")\n", "plt.plot(np.mean(eta_quantum, axis=0)[::2], \".\", label=\"Fully quantum strategy\")\n", "plt.plot(np.mean(eta_classical_quantum,axis=0,)[::2],\".\",label=\"Quantum-classical strategy\",)\n", "plt.xlabel(\"Even epochs\")\n", "plt.ylabel(\"$\\eta$\")\n", "plt.legend()\n", "plt.grid()" ] }, { "cell_type": "markdown", "id": "a4390ce7", "metadata": {}, "source": [ "## Conclusion" ] }, { "attachments": {}, "cell_type": "markdown", "id": "34dd02db", "metadata": {}, "source": [ "Simulating the circuits of [1], we are able to reproduce their results. Data are still noisier than the results due to a smaller number agents. A parallelized version of this code was executed with 10 000 agents and the following results were found:\n", "\n", "![results_10000.png](../_static/img/reinforcement-learning_results_10000.png)\n", "\n", "which are very close to the paper results.\n", "\n", "It's possible to play with the value of $Q_L$ and the number of agents. Remember that the simulation time is linear with the number of agents." ] }, { "cell_type": "markdown", "id": "911dd875", "metadata": {}, "source": [ "## Acknowledgement" ] }, { "cell_type": "markdown", "id": "7721eae9", "metadata": {}, "source": [ "This work was initially done during the Hackathon on Linear Optical Quantum Communication (LOQCathon) organised by [Quandela](https://www.quandela.com/) and [QICS](https://qics.sorbonne-universite.fr/en) (Quantum Information Center of Sorbonne Université), by the team composed of Luís Bugalho, Laura dos Santos Martins, Paolo Fittipaldi, Yoann Piétri and Verena Yacoub. The supervision was provided by Pierre-Emmanuel Emeriau." ] }, { "cell_type": "markdown", "id": "441c442e", "metadata": {}, "source": [ "## References\n", "\n", "> [1] V. Saggio, B. E. Asenbeck, A. Hamann, et al. Experimental quantum speed-up in reinforcement learning agents. [Nature](https://doi.org/10.1038/s41586-021-03242-7), 591(7849), 229-233 (2021).\n", "\n", " > [2] A. Hamann, S. Wölk. Performance analysis of a hybrid agent for quantum-accessible reinforcement learning. [New Journal of Physics](https://doi.org/10.1088/1367-2630/ac5b56 ), 24(3), 033044 (2022)." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Remote_Computation_Tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "345cdcd4fc84356", "metadata": {}, "source": [ "# Remote Computing" ] }, { "cell_type": "markdown", "id": "6ba3240f621f8ae4", "metadata": {}, "source": [ "In this tutorial, we are going to send computations on remote platforms using Perceval. A \"platform\" is an online simulator or an actual physical photonic QPU available through a Cloud provider.\n", "\n", "The default provider is the Quandela Cloud but please note that others exist, see [providers](https://perceval.quandela.net/docs/reference/providers.html) for additional information." ] }, { "cell_type": "markdown", "id": "9196a53b730dcd34", "metadata": {}, "source": [ "## I. Updated Hello World\n", "\n", "Remember the *Hello World* code from the *Getting Started* page? Let's update it in order to run the same simulation on a remote platform." ] }, { "cell_type": "code", "execution_count": 1, "id": "1305dd0510f1fecb", "metadata": {}, "outputs": [], "source": [ "import time\n", "import math\n", "from pprint import pprint\n", "from tqdm.notebook import tqdm\n", "\n", "from perceval import BS, PS, BasicState, RemoteConfig, RemoteProcessor, pdisplay, NoiseModel\n", "from perceval.algorithm import Sampler" ] }, { "cell_type": "code", "execution_count": 2, "id": "9382b93a70440344", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Samples: {\n", " |0,2>: 135\n", " |1,0>: 4841\n", " |1,1>: 22\n", " |2,0>: 127\n", " |0,1>: 4875\n", "}\n", "Probabilities: {\n", "\t|1,1>: 0.001923\n", "\t|1,0>: 0.487179\n", "\t|0,1>: 0.487179\n", "\t|2,0>: 0.011859\n", "\t|0,2>: 0.011859\n", "}\n" ] } ], "source": [ "# The first few lines remain unchanged\n", "input_state = BasicState(\"|1,1>\")\n", "circuit = BS()\n", "noise_model = NoiseModel(transmittance=0.05, indistinguishability=0.85)\n", "\n", "# In this version, the local Processor is replaced by a RemoteProcessor which is constructed slightly differently:\n", "# - The back-end name \"SLOS\" is replaced by the name of the platform we want to send our computation on (here, \"sim:slos\").\n", "# - The 2nd parameter is an authentication token to the Cloud provider we're using (by default, the Quandela Cloud).\n", "# Let's assume we have a valid token, for now. We'll see how to generate one, later on, in this tutorial.\n", "# - Noise parameters can still be set\n", "processor = RemoteProcessor(\"sim:slos\", \"a valid authentication token\", noise=noise_model)\n", "processor.add(0, circuit) # Add the circuit to the remote processor\n", "\n", "# Starting from here, Processor and RemoteProcessor are fully interchangeable\n", "processor.min_detected_photons_filter(1)\n", "processor.with_input(input_state)\n", "\n", "sampler = Sampler(processor, max_shots_per_call=10_000) # Here, the max_shots_per_call parameter is required to limit your credit usage on the Cloud\n", "samples = sampler.sample_count(10_000)['results']\n", "probs = sampler.probs()['results']\n", "print(f\"Samples: {samples}\")\n", "print(f\"Probabilities: {probs}\")" ] }, { "cell_type": "markdown", "id": "6c5dc50ec86e533", "metadata": {}, "source": [ "Similar enough, isn't it?" ] }, { "cell_type": "markdown", "id": "6e24c77b8588ff47", "metadata": {}, "source": [ "## II. Preparing for remote computing\n", "\n", "Connecting to a remote provider requires a bit of configuration. You need to be able to connect and authenticate to the remote service.\n", "\n", "Quandela Cloud authentication is managed through *user tokens*. These are created on the Cloud website, from your user account page. If you haven't done so beforehand, please visit [cloud.quandela.com](https://cloud.quandela.com) and create an account. You'll be able to check what platforms are available to you, as well as their specifications.\n", "You have to generate a token in order to use any platform on the Cloud using a Perceval script. A token is personal and should not be shared. You can create as many as you wish on a single account.\n", "\n", "You can setup your connection information once and for all on a given computer, using Perceval persistent data system:" ] }, { "cell_type": "code", "execution_count": 3, "id": "bfc91a8d9ac67e6c", "metadata": {}, "outputs": [], "source": [ "# Save your token, cloud url and proxy configuration in a RemoteConfig instance, then call save().\n", "# You only need to do this once per machine, data will be shared among different Perceval installs on the same machine.\n", "# If your token changes, you'll need to redo this step once.\n", "remote_config = RemoteConfig()\n", "remote_config.set_token(\"MY_TOKEN\")\n", "remote_config.set_url(\"https://api.cloud.quandela.com\") # Optional, by default the url is https://api.cloud.quandela.com\n", "remote_config.set_proxies({\"https\": \"socks5h://USER:PASSWORD@HOST:PORT\"}) # Optional proxy configuration\n", "remote_config.save()" ] }, { "cell_type": "markdown", "id": "7271586378d04ba7", "metadata": {}, "source": [ "

\n", "If you need to use multiple authentication information within the same script, you will have to enter the token value for each RemoteProcessor, as shown in the updated Hello World.

" ] }, { "cell_type": "markdown", "id": "794ea57eed18d53d", "metadata": {}, "source": [ "## III. Connect to a platform" ] }, { "cell_type": "markdown", "id": "970d809f1751345f", "metadata": {}, "source": [ "Once you have chosen the platform you want your code executed on, all you have to do is to copy its name and define a `RemoteProcessor` with it. Don't forget to give the platform access rights to your token. Note that all simulators platform name start with \"sim:\" where an actual QPU starts with \"qpu:\"." ] }, { "cell_type": "code", "execution_count": 4, "id": "99f5f2583cf30f7f", "metadata": {}, "outputs": [], "source": [ "# A RemoteProcessor works mostly like a regular Processor except that it performs https requests instead of local simulations\n", "remote_simulator = RemoteProcessor(\"sim:belenos\") # For instance, connect to sim:belenos" ] }, { "cell_type": "markdown", "id": "37e01497d5a83f98", "metadata": {}, "source": [ "You can now access to the specifications of the platform directly in Perceval." ] }, { "cell_type": "code", "execution_count": 5, "id": "a2f929876f0126d1", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592586\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_0\n", "\n", "\n", "Φ=phi_1\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592797\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.623139\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_2\n", "\n", "\n", "Φ=phi_3\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611644\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.59696\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_4\n", "\n", "\n", "Φ=phi_5\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.584552\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.568168\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_6\n", "\n", "\n", "Φ=phi_7\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.598542\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.598337\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_8\n", "\n", "\n", "Φ=phi_9\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596465\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.652911\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_10\n", "\n", "\n", "Φ=phi_11\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612695\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.585144\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_12\n", "\n", "\n", "Φ=0\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.587023\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626093\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_14\n", "\n", "\n", "Φ=phi_15\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629949\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607811\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_16\n", "\n", "\n", "Φ=phi_17\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.600041\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.638289\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_18\n", "\n", "\n", "Φ=phi_19\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611996\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.603179\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_20\n", "\n", "\n", "Φ=phi_21\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592454\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595505\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_22\n", "\n", "\n", "Φ=phi_23\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.568221\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_24\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.582686\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_25\n", "\n", "\n", "Φ=phi_26\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.590455\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.593186\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_27\n", "\n", "\n", "Φ=phi_28\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.594594\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.590227\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_29\n", "\n", "\n", "Φ=phi_30\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595946\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607244\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_31\n", "\n", "\n", "Φ=phi_32\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.606909\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.598621\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_33\n", "\n", "\n", "Φ=phi_34\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.635009\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.588136\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_35\n", "\n", "\n", "Φ=phi_36\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602817\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601912\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_37\n", "\n", "\n", "Φ=phi_38\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620931\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608499\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_39\n", "\n", "\n", "Φ=phi_40\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607402\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.584039\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_41\n", "\n", "\n", "Φ=phi_42\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.598781\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.60207\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_43\n", "\n", "\n", "Φ=phi_44\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.600978\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601837\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_45\n", "\n", "\n", "Φ=phi_46\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602657\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_47\n", "\n", "\n", "Φ=0.091725\n", "\n", "\n", "Φ=6.062511\n", "\n", "\n", "Φ=3.919951\n", "\n", "\n", "Φ=0.722609\n", "\n", "\n", "Φ=3.081416\n", "\n", "\n", "Φ=1.397104\n", "\n", "\n", "Φ=4.009651\n", "\n", "\n", "Φ=4.642272\n", "\n", "\n", "Φ=1.970383\n", "\n", "\n", "Φ=4.047059\n", "\n", "\n", "Φ=0.884966\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595237\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_59\n", "\n", "\n", "Φ=phi_60\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595961\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607758\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_61\n", "\n", "\n", "Φ=phi_62\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.62886\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.589911\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_63\n", "\n", "\n", "Φ=phi_64\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619315\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.574114\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_65\n", "\n", "\n", "Φ=phi_66\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.60725\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596477\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_67\n", "\n", "\n", "Φ=phi_68\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615763\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.617572\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_69\n", "\n", "\n", "Φ=phi_70\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.628586\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.594367\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_71\n", "\n", "\n", "Φ=phi_72\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612887\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.62111\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_73\n", "\n", "\n", "Φ=phi_74\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630833\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608123\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_75\n", "\n", "\n", "Φ=phi_76\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.627366\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626664\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_77\n", "\n", "\n", "Φ=phi_78\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.634494\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601534\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_79\n", "\n", "\n", "Φ=phi_80\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620814\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.585688\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_81\n", "\n", "\n", "Φ=phi_82\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.593123\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=3.370915\n", "\n", "\n", "Φ=4.541646\n", "\n", "\n", "Φ=5.797314\n", "\n", "\n", "Φ=5.467524\n", "\n", "\n", "Φ=3.310396\n", "\n", "\n", "Φ=4.065526\n", "\n", "\n", "Φ=6.083924\n", "\n", "\n", "Φ=1.823141\n", "\n", "\n", "Φ=2.111398\n", "\n", "\n", "Φ=3.065357\n", "\n", "\n", "Φ=2.28225\n", "\n", "\n", "Φ=phi_94\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.582861\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_95\n", "\n", "\n", "Φ=phi_96\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611666\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.578025\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_97\n", "\n", "\n", "Φ=phi_98\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612233\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.586291\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_99\n", "\n", "\n", "Φ=phi_100\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.603331\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.598746\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_101\n", "\n", "\n", "Φ=phi_102\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.649709\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595136\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_103\n", "\n", "\n", "Φ=phi_104\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.631984\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.571994\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_105\n", "\n", "\n", "Φ=phi_106\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612849\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.600051\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_107\n", "\n", "\n", "Φ=phi_108\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.62917\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608154\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_109\n", "\n", "\n", "Φ=phi_110\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.659049\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.588221\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_111\n", "\n", "\n", "Φ=phi_112\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.6277\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609127\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_113\n", "\n", "\n", "Φ=phi_114\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.649983\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607758\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_115\n", "\n", "\n", "Φ=phi_116\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.636107\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_117\n", "\n", "\n", "Φ=4.615803\n", "\n", "\n", "Φ=6.085824\n", "\n", "\n", "Φ=2.718745\n", "\n", "\n", "Φ=4.088528\n", "\n", "\n", "Φ=0.842087\n", "\n", "\n", "Φ=1.543133\n", "\n", "\n", "Φ=6.142944\n", "\n", "\n", "Φ=0.539682\n", "\n", "\n", "Φ=5.534988\n", "\n", "\n", "Φ=0.708196\n", "\n", "\n", "Φ=0.833035\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.594884\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_129\n", "\n", "\n", "Φ=phi_130\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620675\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.604855\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_131\n", "\n", "\n", "Φ=phi_132\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.638945\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.585762\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_133\n", "\n", "\n", "Φ=phi_134\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.623788\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.524066\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_135\n", "\n", "\n", "Φ=phi_136\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602074\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.580427\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.154786\n", "\n", "\n", "Φ=phi_138\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613424\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607598\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_139\n", "\n", "\n", "Φ=phi_140\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.628078\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.593086\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_141\n", "\n", "\n", "Φ=phi_142\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613116\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592522\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_143\n", "\n", "\n", "Φ=phi_144\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612773\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.58757\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_145\n", "\n", "\n", "Φ=phi_146\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601192\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619884\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_147\n", "\n", "\n", "Φ=phi_148\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625344\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.588174\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_149\n", "\n", "\n", "Φ=phi_150\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615802\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.586022\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_151\n", "\n", "\n", "Φ=phi_152\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.594063\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.141173\n", "\n", "\n", "Φ=4.48745\n", "\n", "\n", "Φ=3.352222\n", "\n", "\n", "Φ=2.770353\n", "\n", "\n", "Φ=0.99622\n", "\n", "\n", "Φ=4.119772\n", "\n", "\n", "Φ=1.023646\n", "\n", "\n", "Φ=3.773432\n", "\n", "\n", "Φ=5.530842\n", "\n", "\n", "Φ=5.311247\n", "\n", "\n", "Φ=1.788394\n", "\n", "\n", "Φ=phi_164\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.600616\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_165\n", "\n", "\n", "Φ=phi_166\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.624961\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596709\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_167\n", "\n", "\n", "Φ=phi_168\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609041\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.604612\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_169\n", "\n", "\n", "Φ=phi_170\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615055\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.62828\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_171\n", "\n", "\n", "Φ=phi_172\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.641682\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.616333\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_173\n", "\n", "\n", "Φ=phi_174\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.6408\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.597697\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_175\n", "\n", "\n", "Φ=phi_176\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608369\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619128\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_177\n", "\n", "\n", "Φ=phi_178\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625681\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629236\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_179\n", "\n", "\n", "Φ=phi_180\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.655972\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612137\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_181\n", "\n", "\n", "Φ=phi_182\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.62012\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.627444\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_183\n", "\n", "\n", "Φ=phi_184\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.639737\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621463\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_185\n", "\n", "\n", "Φ=phi_186\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630803\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_187\n", "\n", "\n", "Φ=4.185353\n", "\n", "\n", "Φ=3.598293\n", "\n", "\n", "Φ=5.710381\n", "\n", "\n", "Φ=1.270484\n", "\n", "\n", "Φ=0.568651\n", "\n", "\n", "Φ=2.330606\n", "\n", "\n", "Φ=0.981046\n", "\n", "\n", "Φ=3.895598\n", "\n", "\n", "Φ=0.965355\n", "\n", "\n", "Φ=5.965692\n", "\n", "\n", "Φ=0.906368\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.60215\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_199\n", "\n", "\n", "Φ=phi_200\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.59302\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.61778\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_201\n", "\n", "\n", "Φ=phi_202\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621003\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608788\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_203\n", "\n", "\n", "Φ=phi_204\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601946\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.588015\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_205\n", "\n", "\n", "Φ=phi_206\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.600508\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596627\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_207\n", "\n", "\n", "Φ=phi_208\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.599054\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620301\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_209\n", "\n", "\n", "Φ=phi_210\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626583\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.614577\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_211\n", "\n", "\n", "Φ=phi_212\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.604762\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602292\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_213\n", "\n", "\n", "Φ=phi_214\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609893\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595651\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_215\n", "\n", "\n", "Φ=phi_216\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.60499\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.628887\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_217\n", "\n", "\n", "Φ=phi_218\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621553\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592923\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_219\n", "\n", "\n", "Φ=phi_220\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.604233\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.585948\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_221\n", "\n", "\n", "Φ=phi_222\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.584598\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.558792\n", "\n", "\n", "Φ=2.150226\n", "\n", "\n", "Φ=0.705036\n", "\n", "\n", "Φ=0.025012\n", "\n", "\n", "Φ=0.782315\n", "\n", "\n", "Φ=5.296046\n", "\n", "\n", "Φ=2.572739\n", "\n", "\n", "Φ=1.293784\n", "\n", "\n", "Φ=0.757939\n", "\n", "\n", "Φ=4.622469\n", "\n", "\n", "Φ=2.221808\n", "\n", "\n", "Φ=phi_234\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.604165\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_235\n", "\n", "\n", "Φ=phi_236\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620792\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.599179\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_237\n", "\n", "\n", "Φ=phi_238\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.597837\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607995\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_239\n", "\n", "\n", "Φ=phi_240\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602959\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.634994\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_241\n", "\n", "\n", "Φ=phi_242\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630514\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630992\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_243\n", "\n", "\n", "Φ=phi_244\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.627408\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611245\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_245\n", "\n", "\n", "Φ=phi_246\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602339\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.647768\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_247\n", "\n", "\n", "Φ=phi_248\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.635064\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.639908\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_249\n", "\n", "\n", "Φ=phi_250\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.651109\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620079\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_251\n", "\n", "\n", "Φ=phi_252\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629394\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625262\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_253\n", "\n", "\n", "Φ=phi_254\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.636709\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629989\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_255\n", "\n", "\n", "Φ=phi_256\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.632404\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_257\n", "\n", "\n", "Φ=2.291572\n", "\n", "\n", "Φ=0.792206\n", "\n", "\n", "Φ=3.586547\n", "\n", "\n", "Φ=1.624304\n", "\n", "\n", "Φ=1.85231\n", "\n", "\n", "Φ=4.245793\n", "\n", "\n", "Φ=4.98113\n", "\n", "\n", "Φ=5.422766\n", "\n", "\n", "Φ=0.897653\n", "\n", "\n", "Φ=0.409048\n", "\n", "\n", "Φ=5.78361\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.614712\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_269\n", "\n", "\n", "Φ=phi_270\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.628087\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.633069\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_271\n", "\n", "\n", "Φ=phi_272\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.635687\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618619\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_273\n", "\n", "\n", "Φ=phi_274\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621119\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619579\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_275\n", "\n", "\n", "Φ=phi_276\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.631861\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.614657\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_277\n", "\n", "\n", "Φ=phi_278\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626349\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.633688\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_279\n", "\n", "\n", "Φ=phi_280\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.646433\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625857\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_281\n", "\n", "\n", "Φ=phi_282\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630595\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630839\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_283\n", "\n", "\n", "Φ=phi_284\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.648469\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.617413\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_285\n", "\n", "\n", "Φ=phi_286\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626225\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.652917\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_287\n", "\n", "\n", "Φ=phi_288\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.648898\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.61883\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_289\n", "\n", "\n", "Φ=phi_290\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.624878\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621262\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_291\n", "\n", "\n", "Φ=phi_292\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619481\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4.619087\n", "\n", "\n", "Φ=5.324477\n", "\n", "\n", "Φ=4.814724\n", "\n", "\n", "Φ=6.104692\n", "\n", "\n", "Φ=1.440467\n", "\n", "\n", "Φ=0.394809\n", "\n", "\n", "Φ=0.177703\n", "\n", "\n", "Φ=2.208592\n", "\n", "\n", "Φ=0.786532\n", "\n", "\n", "Φ=5.109963\n", "\n", "\n", "Φ=0.544068\n", "\n", "\n", "Φ=phi_304\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615348\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_305\n", "\n", "\n", "Φ=phi_306\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615645\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.599709\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_307\n", "\n", "\n", "Φ=phi_308\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618182\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602918\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_309\n", "\n", "\n", "Φ=phi_310\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619342\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613155\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_311\n", "\n", "\n", "Φ=phi_312\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.624112\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621557\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_313\n", "\n", "\n", "Φ=phi_314\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.627623\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609474\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_315\n", "\n", "\n", "Φ=phi_316\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.59977\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.640323\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_317\n", "\n", "\n", "Φ=phi_318\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630861\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621932\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_319\n", "\n", "\n", "Φ=phi_320\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625979\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609568\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_321\n", "\n", "\n", "Φ=phi_322\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620208\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619176\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_323\n", "\n", "\n", "Φ=phi_324\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.637928\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.631361\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_325\n", "\n", "\n", "Φ=phi_326\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630177\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_327\n", "\n", "\n", "Φ=5.675214\n", "\n", "\n", "Φ=4.418985\n", "\n", "\n", "Φ=3.726329\n", "\n", "\n", "Φ=2.358336\n", "\n", "\n", "Φ=3.329476\n", "\n", "\n", "Φ=1.867048\n", "\n", "\n", "Φ=5.937948\n", "\n", "\n", "Φ=5.434234\n", "\n", "\n", "Φ=0.941115\n", "\n", "\n", "Φ=4.740086\n", "\n", "\n", "Φ=4.784553\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629199\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_339\n", "\n", "\n", "Φ=phi_340\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.623789\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.642837\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_341\n", "\n", "\n", "Φ=phi_342\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.633099\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.634135\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_343\n", "\n", "\n", "Φ=phi_344\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.62252\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.650595\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_345\n", "\n", "\n", "Φ=0\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629693\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629322\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_347\n", "\n", "\n", "Φ=phi_348\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.63048\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.641812\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_349\n", "\n", "\n", "Φ=phi_350\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.649614\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.633144\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_351\n", "\n", "\n", "Φ=phi_352\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.628483\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.661018\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_353\n", "\n", "\n", "Φ=phi_354\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.662803\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629347\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_355\n", "\n", "\n", "Φ=phi_356\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.628473\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.669599\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_357\n", "\n", "\n", "Φ=phi_358\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.653175\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.622716\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_359\n", "\n", "\n", "Φ=phi_360\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621458\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.634522\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_361\n", "\n", "\n", "Φ=phi_362\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626577\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.294078\n", "\n", "\n", "Φ=2.814042\n", "\n", "\n", "Φ=4.937358\n", "\n", "\n", "Φ=0.088515\n", "\n", "\n", "Φ=2.730108\n", "\n", "\n", "Φ=4.450837\n", "\n", "\n", "Φ=1.145756\n", "\n", "\n", "Φ=1.906939\n", "\n", "\n", "Φ=0.520582\n", "\n", "\n", "Φ=2.967723\n", "\n", "\n", "Φ=6.207649\n", "\n", "\n", "Φ=phi_374\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618224\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_375\n", "\n", "\n", "Φ=phi_376\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.61294\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.600633\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_377\n", "\n", "\n", "Φ=phi_378\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615762\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.6128\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_379\n", "\n", "\n", "Φ=phi_380\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619613\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625448\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_381\n", "\n", "\n", "Φ=phi_382\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.635442\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.632342\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_383\n", "\n", "\n", "Φ=phi_384\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.631825\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.605436\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_385\n", "\n", "\n", "Φ=phi_386\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.599548\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618003\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_387\n", "\n", "\n", "Φ=phi_388\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619059\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.6209\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_389\n", "\n", "\n", "Φ=phi_390\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.635125\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.617319\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_391\n", "\n", "\n", "Φ=phi_392\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620493\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.624946\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_393\n", "\n", "\n", "Φ=phi_394\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.6345\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.616989\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_395\n", "\n", "\n", "Φ=phi_396\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619133\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_397\n", "\n", "\n", "Φ=3.191565\n", "\n", "\n", "Φ=5.454361\n", "\n", "\n", "Φ=4.278386\n", "\n", "\n", "Φ=4.03958\n", "\n", "\n", "Φ=1.162822\n", "\n", "\n", "Φ=3.107423\n", "\n", "\n", "Φ=0.032837\n", "\n", "\n", "Φ=5.470453\n", "\n", "\n", "Φ=5.652843\n", "\n", "\n", "Φ=4.616115\n", "\n", "\n", "Φ=0.962434\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.597008\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_409\n", "\n", "\n", "Φ=phi_410\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.600587\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.610567\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_411\n", "\n", "\n", "Φ=phi_412\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.614663\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.599813\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_413\n", "\n", "\n", "Φ=phi_414\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613306\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620295\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_415\n", "\n", "\n", "Φ=phi_416\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607478\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609037\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_417\n", "\n", "\n", "Φ=phi_418\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602124\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619176\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_419\n", "\n", "\n", "Φ=phi_420\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.649562\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609943\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_421\n", "\n", "\n", "Φ=phi_422\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618505\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.633532\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_423\n", "\n", "\n", "Φ=phi_424\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.63614\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608099\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_425\n", "\n", "\n", "Φ=phi_426\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611696\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.616148\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_427\n", "\n", "\n", "Φ=phi_428\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.633834\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602301\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_429\n", "\n", "\n", "Φ=phi_430\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.617693\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608037\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_431\n", "\n", "\n", "Φ=phi_432\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615439\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.332574\n", "\n", "\n", "Φ=3.987601\n", "\n", "\n", "Φ=5.394751\n", "\n", "\n", "Φ=2.075769\n", "\n", "\n", "Φ=0.650847\n", "\n", "\n", "Φ=5.666758\n", "\n", "\n", "Φ=1.57315\n", "\n", "\n", "Φ=2.386669\n", "\n", "\n", "Φ=4.987086\n", "\n", "\n", "Φ=2.697946\n", "\n", "\n", "Φ=2.196592\n", "\n", "\n", "Φ=phi_444\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.623536\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_445\n", "\n", "\n", "Φ=phi_446\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.631439\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.605621\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_447\n", "\n", "\n", "Φ=phi_448\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613996\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619864\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_449\n", "\n", "\n", "Φ=phi_450\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625055\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630756\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_451\n", "\n", "\n", "Φ=phi_452\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.63769\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.64362\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_453\n", "\n", "\n", "Φ=phi_454\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.628151\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602914\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_455\n", "\n", "\n", "Φ=phi_456\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601459\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621867\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_457\n", "\n", "\n", "Φ=phi_458\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.627493\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.63295\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_459\n", "\n", "\n", "Φ=phi_460\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.645437\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629781\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_461\n", "\n", "\n", "Φ=phi_462\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.636864\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.642462\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_463\n", "\n", "\n", "Φ=phi_464\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.644762\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613274\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_465\n", "\n", "\n", "Φ=phi_466\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.63339\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_467\n", "\n", "\n", "Φ=3.97303\n", "\n", "\n", "Φ=5.725366\n", "\n", "\n", "Φ=5.850409\n", "\n", "\n", "Φ=1.916151\n", "\n", "\n", "Φ=1.887018\n", "\n", "\n", "Φ=3.398157\n", "\n", "\n", "Φ=6.05039\n", "\n", "\n", "Φ=3.620631\n", "\n", "\n", "Φ=4.957586\n", "\n", "\n", "Φ=0.265374\n", "\n", "\n", "Φ=5.555218\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596703\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_479\n", "\n", "\n", "Φ=phi_480\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.599579\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613258\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_481\n", "\n", "\n", "Φ=phi_482\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619123\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.604806\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_483\n", "\n", "\n", "Φ=phi_484\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608512\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.622448\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_485\n", "\n", "\n", "Φ=phi_486\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.604041\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608794\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_487\n", "\n", "\n", "Φ=phi_488\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609131\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626359\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_489\n", "\n", "\n", "Φ=phi_490\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.639687\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.616587\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_491\n", "\n", "\n", "Φ=phi_492\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.632276\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.632161\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_493\n", "\n", "\n", "Φ=phi_494\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.627169\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.605865\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_495\n", "\n", "\n", "Φ=phi_496\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615489\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615122\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_497\n", "\n", "\n", "Φ=phi_498\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.643796\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618924\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_499\n", "\n", "\n", "Φ=phi_500\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.624032\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625537\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_501\n", "\n", "\n", "Φ=phi_502\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.63514\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.228044\n", "\n", "\n", "Φ=4.573392\n", "\n", "\n", "Φ=0.84455\n", "\n", "\n", "Φ=0.647811\n", "\n", "\n", "Φ=1.499444\n", "\n", "\n", "Φ=0.285635\n", "\n", "\n", "Φ=1.499219\n", "\n", "\n", "Φ=1.068799\n", "\n", "\n", "Φ=4.833049\n", "\n", "\n", "Φ=5.54483\n", "\n", "\n", "Φ=0.487943\n", "\n", "\n", "Φ=phi_514\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.633275\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_515\n", "\n", "\n", "Φ=phi_516\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.635906\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607537\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_517\n", "\n", "\n", "Φ=phi_518\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.608636\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625665\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_519\n", "\n", "\n", "Φ=phi_520\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611398\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.6336\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_521\n", "\n", "\n", "Φ=phi_522\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.628556\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.640565\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_523\n", "\n", "\n", "Φ=phi_524\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.631528\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.603577\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_525\n", "\n", "\n", "Φ=phi_526\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595859\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.627423\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_527\n", "\n", "\n", "Φ=phi_528\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626279\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.643914\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_529\n", "\n", "\n", "Φ=phi_530\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.639521\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626569\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_531\n", "\n", "\n", "Φ=phi_532\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629441\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.644903\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_533\n", "\n", "\n", "Φ=phi_534\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.641721\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.636014\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_535\n", "\n", "\n", "Φ=phi_536\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.624336\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_537\n", "\n", "\n", "Φ=4.691996\n", "\n", "\n", "Φ=0.927446\n", "\n", "\n", "Φ=4.352925\n", "\n", "\n", "Φ=2.75105\n", "\n", "\n", "Φ=2.980841\n", "\n", "\n", "Φ=3.488902\n", "\n", "\n", "Φ=4.795209\n", "\n", "\n", "Φ=3.690878\n", "\n", "\n", "Φ=1.301007\n", "\n", "\n", "Φ=5.001578\n", "\n", "\n", "Φ=4.638359\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.593408\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_549\n", "\n", "\n", "Φ=phi_550\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.589159\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609445\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_551\n", "\n", "\n", "Φ=phi_552\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611615\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.590411\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_553\n", "\n", "\n", "Φ=phi_554\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.58523\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615168\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_555\n", "\n", "\n", "Φ=phi_556\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.584317\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.584473\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_557\n", "\n", "\n", "Φ=phi_558\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595616\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611585\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_559\n", "\n", "\n", "Φ=phi_560\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.623681\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.606251\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_561\n", "\n", "\n", "Φ=phi_562\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611282\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.610641\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_563\n", "\n", "\n", "Φ=phi_564\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611912\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.588204\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_565\n", "\n", "\n", "Φ=phi_566\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596505\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602705\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_567\n", "\n", "\n", "Φ=phi_568\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.617756\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596143\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_569\n", "\n", "\n", "Φ=phi_570\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.606833\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601257\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_571\n", "\n", "\n", "Φ=phi_572\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618369\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=0.572241\n", "\n", "\n", "Φ=5.351602\n", "\n", "\n", "Φ=5.433839\n", "\n", "\n", "Φ=1.231296\n", "\n", "\n", "Φ=2.270514\n", "\n", "\n", "Φ=0.071392\n", "\n", "\n", "Φ=5.6676\n", "\n", "\n", "Φ=0.998511\n", "\n", "\n", "Φ=0.593415\n", "\n", "\n", "Φ=3.86419\n", "\n", "\n", "Φ=5.426534\n", "\n", "\n", "Φ=phi_584\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629701\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_585\n", "\n", "\n", "Φ=phi_586\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.626539\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.599851\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_587\n", "\n", "\n", "Φ=phi_588\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.624164\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613376\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_589\n", "\n", "\n", "Φ=phi_590\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.610684\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618276\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_591\n", "\n", "\n", "Φ=phi_592\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630678\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629969\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_593\n", "\n", "\n", "Φ=phi_594\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.632002\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.591989\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_595\n", "\n", "\n", "Φ=phi_596\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.59512\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.61116\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_597\n", "\n", "\n", "Φ=phi_598\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618988\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.639025\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_599\n", "\n", "\n", "Φ=phi_600\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.639586\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.60931\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_601\n", "\n", "\n", "Φ=phi_602\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.61484\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.637825\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_603\n", "\n", "\n", "Φ=phi_604\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.633303\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.627262\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_605\n", "\n", "\n", "Φ=phi_606\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618893\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_607\n", "\n", "\n", "Φ=5.337973\n", "\n", "\n", "Φ=5.433947\n", "\n", "\n", "Φ=4.83599\n", "\n", "\n", "Φ=3.496087\n", "\n", "\n", "Φ=2.57139\n", "\n", "\n", "Φ=1.397877\n", "\n", "\n", "Φ=4.547445\n", "\n", "\n", "Φ=5.624399\n", "\n", "\n", "Φ=5.785348\n", "\n", "\n", "Φ=3.598378\n", "\n", "\n", "Φ=0.523983\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.585379\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_619\n", "\n", "\n", "Φ=phi_620\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.590428\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.606048\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_621\n", "\n", "\n", "Φ=phi_622\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.610192\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.593848\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_623\n", "\n", "\n", "Φ=phi_624\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592999\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.6055\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_625\n", "\n", "\n", "Φ=phi_626\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595244\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596452\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_627\n", "\n", "\n", "Φ=phi_628\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.58801\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619867\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_629\n", "\n", "\n", "Φ=phi_630\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625853\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601533\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_631\n", "\n", "\n", "Φ=phi_632\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611395\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612584\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_633\n", "\n", "\n", "Φ=phi_634\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630505\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.58804\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_635\n", "\n", "\n", "Φ=phi_636\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.59917\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602013\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_637\n", "\n", "\n", "Φ=phi_638\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611539\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.605878\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_639\n", "\n", "\n", "Φ=phi_640\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.606588\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.606366\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_641\n", "\n", "\n", "Φ=phi_642\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607228\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=1.073972\n", "\n", "\n", "Φ=4.017564\n", "\n", "\n", "Φ=5.651472\n", "\n", "\n", "Φ=1.908744\n", "\n", "\n", "Φ=1.871838\n", "\n", "\n", "Φ=4.175673\n", "\n", "\n", "Φ=5.61981\n", "\n", "\n", "Φ=2.788674\n", "\n", "\n", "Φ=5.039259\n", "\n", "\n", "Φ=2.66901\n", "\n", "\n", "Φ=1.400883\n", "\n", "\n", "Φ=phi_654\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.623777\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_655\n", "\n", "\n", "Φ=phi_656\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.603952\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602044\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_657\n", "\n", "\n", "Φ=phi_658\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.604273\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.607736\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_659\n", "\n", "\n", "Φ=phi_660\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.610943\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.625583\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_661\n", "\n", "\n", "Φ=phi_662\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.628506\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.634206\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_663\n", "\n", "\n", "Φ=phi_664\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.636944\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.594777\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_665\n", "\n", "\n", "Φ=phi_666\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.594606\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611028\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_667\n", "\n", "\n", "Φ=phi_668\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612482\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629986\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_669\n", "\n", "\n", "Φ=phi_670\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629495\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611953\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_671\n", "\n", "\n", "Φ=phi_672\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621435\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.627781\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_673\n", "\n", "\n", "Φ=phi_674\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.617602\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613333\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_675\n", "\n", "\n", "Φ=phi_676\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609059\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_677\n", "\n", "\n", "Φ=4.049476\n", "\n", "\n", "Φ=5.808081\n", "\n", "\n", "Φ=5.82074\n", "\n", "\n", "Φ=3.168835\n", "\n", "\n", "Φ=0.654517\n", "\n", "\n", "Φ=1.494592\n", "\n", "\n", "Φ=0.135652\n", "\n", "\n", "Φ=4.0086\n", "\n", "\n", "Φ=4.589504\n", "\n", "\n", "Φ=6.141926\n", "\n", "\n", "Φ=1.198869\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596053\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_689\n", "\n", "\n", "Φ=phi_690\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.58317\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.598894\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_691\n", "\n", "\n", "Φ=phi_692\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592748\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.588716\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_693\n", "\n", "\n", "Φ=phi_694\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.584349\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592699\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_695\n", "\n", "\n", "Φ=phi_696\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.591847\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.585666\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_697\n", "\n", "\n", "Φ=phi_698\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.578306\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615629\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_699\n", "\n", "\n", "Φ=phi_700\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.61974\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.605071\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_701\n", "\n", "\n", "Φ=phi_702\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596812\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619615\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_703\n", "\n", "\n", "Φ=phi_704\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620515\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.602219\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_705\n", "\n", "\n", "Φ=phi_706\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.610279\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.600038\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_707\n", "\n", "\n", "Φ=phi_708\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619255\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.604302\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_709\n", "\n", "\n", "Φ=phi_710\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.600481\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595916\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_711\n", "\n", "\n", "Φ=phi_712\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.59632\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=5.761056\n", "\n", "\n", "Φ=4.310289\n", "\n", "\n", "Φ=0.35295\n", "\n", "\n", "Φ=1.36928\n", "\n", "\n", "Φ=6.013889\n", "\n", "\n", "Φ=4.224243\n", "\n", "\n", "Φ=1.039488\n", "\n", "\n", "Φ=1.222437\n", "\n", "\n", "Φ=3.59808\n", "\n", "\n", "Φ=4.972179\n", "\n", "\n", "Φ=2.149586\n", "\n", "\n", "Φ=phi_724\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.611409\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_725\n", "\n", "\n", "Φ=phi_726\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.605433\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596972\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_727\n", "\n", "\n", "Φ=phi_728\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595136\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601256\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_729\n", "\n", "\n", "Φ=phi_730\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601873\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619702\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_731\n", "\n", "\n", "Φ=phi_732\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.610962\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.629541\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_733\n", "\n", "\n", "Φ=phi_734\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.633257\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.585835\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_735\n", "\n", "\n", "Φ=phi_736\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.574764\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.61216\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_737\n", "\n", "\n", "Φ=phi_738\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.60368\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.630809\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_739\n", "\n", "\n", "Φ=phi_740\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.620882\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.603426\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_741\n", "\n", "\n", "Φ=phi_742\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.60496\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.632925\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_743\n", "\n", "\n", "Φ=phi_744\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.624605\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.618402\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_745\n", "\n", "\n", "Φ=phi_746\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.613238\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_747\n", "\n", "\n", "Φ=4.259158\n", "\n", "\n", "Φ=0.735016\n", "\n", "\n", "Φ=5.175208\n", "\n", "\n", "Φ=1.507376\n", "\n", "\n", "Φ=0.74185\n", "\n", "\n", "Φ=3.224648\n", "\n", "\n", "Φ=5.104248\n", "\n", "\n", "Φ=2.878192\n", "\n", "\n", "Φ=0.711875\n", "\n", "\n", "Φ=0.778187\n", "\n", "\n", "Φ=4.846328\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.589784\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_759\n", "\n", "\n", "Φ=phi_760\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.5869\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.586158\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_761\n", "\n", "\n", "Φ=phi_762\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.584642\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.572741\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_763\n", "\n", "\n", "Φ=phi_764\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.586001\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.591314\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_765\n", "\n", "\n", "Φ=phi_766\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.594735\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.552901\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_767\n", "\n", "\n", "Φ=phi_768\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.572218\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.624709\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_769\n", "\n", "\n", "Φ=phi_770\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601741\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.567272\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_771\n", "\n", "\n", "Φ=phi_772\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.587419\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612775\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_773\n", "\n", "\n", "Φ=phi_774\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.609938\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.585658\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_775\n", "\n", "\n", "Φ=phi_776\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.588797\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.589232\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_777\n", "\n", "\n", "Φ=phi_778\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.605817\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.593921\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_779\n", "\n", "\n", "Φ=phi_780\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.591815\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.586503\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_781\n", "\n", "\n", "Φ=phi_782\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.586553\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.612847\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_783\n", "\n", "\n", "Φ=phi_784\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.614271\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.593426\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_785\n", "\n", "\n", "Φ=phi_786\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.593686\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.590606\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_787\n", "\n", "\n", "Φ=phi_788\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.595085\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596385\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_789\n", "\n", "\n", "Φ=phi_790\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.60305\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.621837\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_791\n", "\n", "\n", "Φ=phi_792\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.631618\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.565172\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_793\n", "\n", "\n", "Φ=phi_794\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.596348\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.615173\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_795\n", "\n", "\n", "Φ=phi_796\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619946\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.601802\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_797\n", "\n", "\n", "Φ=phi_798\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.588399\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.588965\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_799\n", "\n", "\n", "Φ=phi_800\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592559\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619947\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_801\n", "\n", "\n", "Φ=phi_802\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.619828\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.592843\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=phi_803\n", "\n", "\n", "Φ=phi_804\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.610117\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n", "10\n", "11\n", "12\n", "13\n", "14\n", "15\n", "16\n", "17\n", "18\n", "19\n", "20\n", "21\n", "22\n", "23\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n", "10\n", "11\n", "12\n", "13\n", "14\n", "15\n", "16\n", "17\n", "18\n", "19\n", "20\n", "21\n", "22\n", "23\n", "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "specs = remote_simulator.specs\n", "pdisplay(specs[\"specific_circuit\"])" ] }, { "cell_type": "code", "execution_count": 6, "id": "455b1d968c926cc1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Platform constraints:\n", "{'max_mode_count': 24,\n", " 'max_photon_count': 12,\n", " 'min_mode_count': 1,\n", " 'min_photon_count': 1,\n", " 'support_multi_photon': False}\n", "\n", "Platform supported parameters:\n", "{'final_mode_number': 'number of modes of the output states. States having a '\n", " 'photon on unused modes will be ignored. Useful when '\n", " 'using computed circuits (default input_state.m)',\n", " 'min_detected_photons': 'minimum number of detected photons to keep a state '\n", " '(default input_state.n)',\n", " 'use_photon_recycling': 'boolean enabling the Photon Recycling noise '\n", " 'mitigation process. (default enabled whenever it can '\n", " 'be used)'}\n" ] } ], "source": [ "print(\"Platform constraints:\")\n", "pprint(specs[\"constraints\"])\n", "print(\"\\nPlatform supported parameters:\")\n", "pprint(specs[\"parameters\"])" ] }, { "cell_type": "markdown", "id": "c65bbb0193c33d30", "metadata": {}, "source": [ "## IV. Run a remote simulation\n", "\n", "Now, we can setup our computation. In order to give a value to a \"platform supported parameter\", you have to use a special `set_parameter` function (or `set_parameters` to set multiple at once)." ] }, { "cell_type": "code", "execution_count": 7, "id": "bfb46c3317dd20ff", "metadata": {}, "outputs": [], "source": [ "input_state = BasicState([1, 1])\n", "c = BS() // PS(phi=math.pi/4) // BS()\n", "\n", "remote_simulator.set_circuit(c)\n", "remote_simulator.min_detected_photons_filter(1) # Output state filtering on the basis of detected photons\n", "remote_simulator.with_input(input_state)\n", "\n", "remote_simulator.noise = NoiseModel(indistinguishability=.95, transmittance=.1, g2=.01) # You can inject noise after the object creation" ] }, { "cell_type": "markdown", "id": "572d8844de877204", "metadata": {}, "source": [ "We can now use the `Sampler` with our `RemoteProcessor`. You have to set a maximum shots threshold (`max_shots_per_call` named parameter) when creating a `Sampler` with a remote platform. Local simulations do not require this threshold.\n", "A shot is any detected event containing at least one photon, it is easy to explain, easy to measure. This shot threshold will prevent the user from consuming too many QPU resources, as once it's reached, the acquisition stops. Shots up to this threshold can be reached for all jobs generated by `Sampler` calls (e.g. calling `sample_count` thrice can lead to the use of at most `3*max_shots_per_call` shots)." ] }, { "cell_type": "code", "execution_count": 8, "id": "36dba3741e20d332", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "dcb21813-8648-49e8-841c-e21889e94af1\n" ] } ], "source": [ "nsamples = 200000\n", "sampler = Sampler(remote_simulator, max_shots_per_call=nsamples) # You have to set a 'max_shots_per_call' named parameter\n", "# Here, with `min_detected_photons_filter` set to 1, all shots are de facto samples of interest.\n", "# Thus, in this particular case, the expected sample number can be used as the shots threshold.\n", "\n", "sampler.default_job_name = \"My sampling job\" # All jobs created by this sampler instance will have this custom name on the cloud\n", "\n", "# Create a job a run it asynchronously (with execute_async method). Calling execute_sync, or __call__ would run the job synchronously\n", "# and would thus block the script execution until the job ends and results are returned.\n", "remote_job = sampler.sample_count.execute_async(nsamples)\n", "print(remote_job.id) # Once created, the job was assigned a unique id" ] }, { "cell_type": "markdown", "id": "c54be7990bdaf390", "metadata": {}, "source": [ "The request has now been sent to a remote platform through the cloud. As it is asynchronous, other computations can be performed locally before the results are retrieved. In this example, let's just wait for the end of the computation. If you go to the Quandela Cloud website again, you can now see the job and its progress." ] }, { "cell_type": "code", "execution_count": 9, "id": "39cfe21ec5b2143", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "5623d52788dc400885b239128995df07", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| |" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Job status = SUCCESS\n" ] } ], "source": [ "previous_prog = 0\n", "with tqdm(total=1, bar_format='{desc}{percentage:3.0f}%|{bar}|') as tq:\n", " tq.set_description(f'Get {nsamples} samples from {remote_simulator.name}')\n", " while not remote_job.is_complete:\n", " tq.update(remote_job.status.progress/100-previous_prog)\n", " previous_prog = remote_job.status.progress/100\n", " time.sleep(1)\n", " tq.update(1-previous_prog)\n", " tq.close()\n", "\n", "print(f\"Job status = {remote_job.status()}\")" ] }, { "cell_type": "markdown", "id": "331a09e7a4403e83", "metadata": {}, "source": [ "Once the previous cell has run to the end, the job is finished (again, you can see its status on the website). Let's retrieve the results to do some computation. In this case, the computation is expected to be fast (unless the simulator is unavailable or there are a lot of jobs queued), so we can use the `remote_job` object we created previously. If the computation lasted for a long time, we could have shut down our computer, then turn it back on and finally created a new job object by directly retrieving the results. The *job id* which is visible on the website, is required to resume a job and load its results." ] }, { "cell_type": "code", "execution_count": 10, "id": "ba247265fa7ecae4", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state count
|1,0> 95051
|0,1> 94441
|1,1> 5465
|0,2> 2551
|2,0> 2484
|2,1> 5
|1,2> 3
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "''' # To retrieve your job using a job id\n", "remote_processor = pcvl.RemoteProcessor(\"sim:belenos\")\n", "remote_job = remote_processor.resume_job(id)\n", "'''\n", "\n", "results = remote_job.get_results()\n", "pdisplay(results['results'])" ] }, { "cell_type": "markdown", "id": "ee603a4090a992ba", "metadata": {}, "source": [ "## V. Use a real photonic QPU\n", "\n", "You can run the same sampling on the corresponding QPU. In order to manage your QPU credits, you can estimate the number of shots you'd need for a particular data acquisition. Please note that the maximum shots and maximum samples number act as a dual threshold system. As soon as one of these thresholds is exceeded, the acquisition stops and the results are returned." ] }, { "cell_type": "code", "execution_count": 11, "id": "9946337fddffc740", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "With this setup:\n", "To gather 200000 2-photon coincidences on sim:belenos, you would need around 12933333 shots.\n", "To gather 200000 photon events (with at least 1 photon) on sim:belenos, you would need exactly 200000 shots.\n" ] } ], "source": [ "qpu_platform_name = \"qpu:belenos\"\n", "nsamples = 200000\n", "\n", "remote_qpu = RemoteProcessor(qpu_platform_name)\n", "remote_qpu.set_circuit(c)\n", "remote_qpu.with_input(input_state)\n", "\n", "print(\"With this setup:\")\n", "remote_qpu.min_detected_photons_filter(2)\n", "required_shots = remote_qpu.estimate_required_shots(nsamples=nsamples)\n", "print(f\"To gather {nsamples} 2-photon coincidences on {qpu_platform_name}, you would need around {required_shots} shots.\")\n", "\n", "remote_qpu.min_detected_photons_filter(1)\n", "required_shots = remote_qpu.estimate_required_shots(nsamples=nsamples)\n", "print(f\"To gather {nsamples} photon events (with at least 1 photon) on {qpu_platform_name}, you would need exactly {required_shots} shots.\")" ] }, { "cell_type": "code", "execution_count": 12, "id": "d0d4da5eb034776e", "metadata": {}, "outputs": [], "source": [ "sampler_on_qpu = Sampler(remote_qpu, max_shots_per_call=nsamples)\n", "\n", "remote_job = sampler_on_qpu.sample_count\n", "remote_job.name = \"QPU sampling\" # You may also specify a name to individual jobs\n", "remote_job.execute_async(nsamples);" ] }, { "cell_type": "code", "execution_count": 13, "id": "9c6190e4cc167588", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "cbbee9c15c1344219eddb5adc745364b", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| |" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Job status = SUCCESS\n" ] } ], "source": [ "previous_prog = 0\n", "with tqdm(total=1, bar_format='{desc}{percentage:3.0f}%|{bar}|') as tq:\n", " tq.set_description(f'Get {nsamples} samples from {remote_qpu.name}')\n", " while not remote_job.is_complete:\n", " tq.update(remote_job.status.progress/100-previous_prog)\n", " previous_prog = remote_job.status.progress/100\n", " time.sleep(1)\n", " tq.update(1-previous_prog)\n", " tq.close()\n", "\n", "print(f\"Job status = {remote_job.status()}\")" ] }, { "cell_type": "code", "execution_count": 14, "id": "27f45446c13e8c58", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state count
|0,1> 97117
|1,0> 96864
|1,1> 3234
|0,2> 1395
|2,0> 1388
|1,2> 2
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "results = remote_job.get_results()\n", "pdisplay(results['results'])" ] }, { "cell_type": "markdown", "id": "994f2f5f5bfd9479", "metadata": {}, "source": [ "Now, you know the basic ways to compute results using Quandela Cloud." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Shor_Implementation.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "c2f4fd6c", "metadata": {}, "source": [ "# Shor's algorithm in linear optics" ] }, { "cell_type": "markdown", "id": "5162c9b8", "metadata": {}, "source": [ "This notebook reproduces a simulation in Perceval of a 4-qubit 12-modes optical experiment performing Shor's algorithm, based on Alberto Politi, Jonathan C.F. Matthews, and Jeremy L. O'brien. \"Shor’s quantum factoring algorithm on a photonic chip.\" [Science](https://www.science.org/doi/10.1126/science.1173731) 325.5945 (2009): 1221-1221." ] }, { "cell_type": "markdown", "id": "f29e5abf", "metadata": {}, "source": [ "## Shor's algorithm" ] }, { "cell_type": "markdown", "id": "5dd3a2a5", "metadata": {}, "source": [ "The purpose of Shor's algorithm is to find nontrivial factors of a given number $N$ in polynomial time, yielding an near-exponential speedup compared to state of the art classical algorithms.\n", "\n", "The main routine of Shor's algorithm consists in finding the order $r$ of a number $a \\in \\mathbb{Z}_N$, i.e. the smallest integer $r$ such that $a^r = 1 \\pmod N$.\n", "\n", "If the order of a randomly chosen $a$ which is coprime with $N$ is even, then $(a^{r/2} - 1)(a^{r/2} + 1) = k N$. If none of these factors are multiples of $N$, then $\\gcd(N, a^{r/2} - 1)$ and $\\gcd(N, a^{r/2} + 1)$ are nontrivial factors of $N$." ] }, { "cell_type": "markdown", "id": "ff756302", "metadata": {}, "source": [ "## Preliminaries" ] }, { "cell_type": "code", "execution_count": 1, "id": "55fe7a18", "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "from perceval.algorithm import Analyzer\n", "from perceval import Experiment, Port, Encoding, catalog, FockState, LogicalState, BS, PostSelect, Processor" ] }, { "cell_type": "markdown", "id": "826377de", "metadata": {}, "source": [ "### Path encoding functions\n", "\n", "The following functions allow for conversion between the qubit and Fock state representations." ] }, { "cell_type": "code", "execution_count": 2, "id": "55ee4ce8", "metadata": {}, "outputs": [], "source": [ "def to_logical_state(fock_state):\n", " logical_repr = []\n", " for i in range(fock_state.m // 2):\n", " if fock_state[2*i:2*i+2] == FockState([1, 0]):\n", " logical_repr.append(0)\n", " elif fock_state[2*i:2*i+2] == FockState([0, 1]):\n", " logical_repr.append(1)\n", " else:\n", " raise ValueError(f\"Fock state {fock_state} has no logical representation\")\n", " return LogicalState(logical_repr)" ] }, { "cell_type": "markdown", "id": "016e2534", "metadata": {}, "source": [ "## The circuit" ] }, { "cell_type": "markdown", "id": "01c60397", "metadata": {}, "source": [ "### Quantum circuit\n", "\n", "The quantum circuit has been optimized after choosing parameters $N = 15$ and $a = 2$, and aims to calculate $r=4$.\n", "It features 5 qubits labelled $x_0, x_1, x_2$ and $f_1, f_2$. Qubits $x_i$ and $f_1$ are initially in state $|0\\rangle$, and $f_2$ in state $|1\\rangle$.\n", "In the non-optimised Shor algorithm, qubits $x_i$ encode a binary number representing a pre-image of the Modular Exponentiation Function (MEF) $x \\mapsto a^x \\pmod N$, while qubits $f_i$ hold the image obtained after applying the MEF to qubits $x_i$. Applying the MEF when qubits $x_i$ hold a superposition of different pre-images (obtained with H gates on qubits $x_i$) allows to efficiently compute the order $r$ of parameter $a$ modulo $N$.\n", "\n", "The circuit consists of $\\mathsf{H}$ gates being first applied to each $x_i$ qubit, followed by $\\mathsf{CNOT}$ gates applied on $x_1, f_1$ and $x_2, f_2$ pairs, where $x_i$ are control qubits; finally the inverse $\\mathsf{QFT}$ algorithm is applied on qubits $x_i$.\n", "\n", "$\\mathsf{CNOT}$ gates on $x_i, f_i$ pairs ($x_i$ being the control) are implemented using $\\mathsf{H}$ and $\\mathsf{CZ}$ gates: the $\\mathsf{CZ}$ gate is sandwiched between two applications of $\\mathsf{H}$ on $f_i$.\n", "\n", "The input state of the circuit after optimisation is $|0\\rangle_{x_0}|0\\rangle_{x_1}|0\\rangle_{x_2}|0\\rangle_{f_1}|1\\rangle_{f_2}$.\n", "\n", "The expected output state is then $\\frac{1}{2} |0\\rangle_{x_0} \\otimes \\left ( |0\\rangle_{x_1}|0\\rangle_{f_1} + |1\\rangle_{x_1}|1\\rangle_{f_1} \\right ) \\otimes \\left ( |0\\rangle_{x_2}|1\\rangle_{f_2} + |1\\rangle_{x_2}|0\\rangle_{f_2} \\right )$." ] }, { "cell_type": "markdown", "id": "b7b25cf5", "metadata": {}, "source": [ "### Photonic circuit\n", "\n", "The optical circuit from the result by Politi et al features twelve modes (ordered from 0 to 11 from top to bottom).\n", "\n", "During the execution, qubit $x_0$ remains unentangled from the other qubits. It can therefore be removed from the optical implementation.\n", "\n", "The qubits $x_1, x_2, f_1, f_2$ are path encoded as modes $(0, 1)$, $(4, 5)$, $(2, 3)$, $(6, 7)$ respectively. Four other modes are used as ancillary modes to implement the $\\mathsf{CZ}$ gates.\n", "\n", "With path encoding each $\\mathsf{H}$ gate in the quantum circuit is implemented with a beam splitter with reflectivity $R=1/2$ between the two paths corresponding to the qubit. In our implementation in Perceval, phase shifters are added to properly tune the phase between each path.\n", "\n", "$\\mathsf{CZ}$ gates are implemented with three beam splitters with reflectivity $R=2/3$ acting on six modes: one inner beam splitter creates interference between the two qubits, and two outer beam splitters balance detection probability using auxiliary modes.\n", "This optical implementation successfully yields the output state produced by a $\\mathsf{CZ}$ gate with probability 1/9; otherwise it creates a dummy state, which will be automatically removed by post-selection.\n", "\n", "In the case $r=4$ the QFT can be performed classically and doesn't need to be implemented in the photonic circuit." ] }, { "cell_type": "markdown", "id": "3acb2273", "metadata": {}, "source": [ "## In perceval" ] }, { "cell_type": "markdown", "id": "7aa60c25", "metadata": {}, "source": [ "### Implementing the circuit" ] }, { "cell_type": "code", "execution_count": 3, "id": "f5d2d8b6b53d3fcd", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Optical circuit for Shor's algorithm\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSED CZ\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSED CZ\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "[x1]\n", "\n", "[f1]\n", "\n", "[herald2]\n", "0\n", "\n", "[herald3]\n", "0\n", "\n", "[x2]\n", "\n", "[f2]\n", "\n", "[herald6]\n", "0\n", "\n", "[herald7]\n", "0\n", "\n", "[x1]\n", "\n", "[f1]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "\n", "[x2]\n", "\n", "[f2]\n", "\n", "[herald4]\n", "0\n", "\n", "[herald5]\n", "0\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def create_cz(port_names):\n", " cz_circuit = catalog[\"postprocessed cz\"].build_circuit()\n", " cz = Experiment(cz_circuit)\n", " (cz.add_port(0, Port(Encoding.DUAL_RAIL, port_names[0])) # Add 1st dual rail encoded qubit\n", " .add_port(2, Port(Encoding.DUAL_RAIL, port_names[1])) # Add 2nd dual rail encoded qubit\n", " # Add ancillary modes expecting 0 detected photon\n", " .add_herald(4, 0)\n", " .add_herald(5, 0))\n", " return cz\n", "\n", "\n", "# Create the experiment implementing Shor algorithm\n", "experiment = Experiment(8)\n", "cz1 = create_cz([\"x1\", \"f1\"])\n", "cz2 = create_cz([\"x2\", \"f2\"])\n", "h = BS.H()\n", "\n", "for i in range(0, 8, 2):\n", " experiment.add(i, h)\n", "\n", "experiment.add(0, cz1)\n", "experiment.add(4, cz2)\n", "\n", "experiment.add(2, h)\n", "experiment.add(6, h)\n", "\n", "# Set up a post-selection function ensuring dual rail encoding\n", "experiment.set_postselection(PostSelect(\"[0,1]==1 and [2,3]==1 and [4,5]==1 and [6,7]==1\"))\n", "\n", "print(\"Optical circuit for Shor's algorithm\")\n", "pcvl.pdisplay(experiment, recursive=True)" ] }, { "cell_type": "code", "execution_count": 4, "id": "00d193de", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The associated matrix\n" ] }, { "data": { "text/latex": [ "$\\displaystyle \\left[\\begin{array}{cccccccccccc}\\frac{\\sqrt{6}}{6} & \\frac{\\sqrt{6}}{6} & 0 & 0 & 0 & 0 & 0 & 0 & \\frac{\\sqrt{6}}{3} & 0 & 0 & 0\\\\- \\frac{\\sqrt{6}}{6} & \\frac{\\sqrt{6}}{6} & \\frac{\\sqrt{3}}{3} & \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\\\frac{\\sqrt{6}}{6} & - \\frac{\\sqrt{6}}{6} & \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & 0 & 0 & 0 & \\frac{\\sqrt{3}}{3} & 0 & 0\\\\\\frac{\\sqrt{6}}{6} & - \\frac{\\sqrt{6}}{6} & 0 & \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & 0 & 0 & - \\frac{\\sqrt{3}}{3} & 0 & 0\\\\0 & 0 & 0 & 0 & \\frac{\\sqrt{6}}{6} & \\frac{\\sqrt{6}}{6} & 0 & 0 & 0 & 0 & \\frac{\\sqrt{6}}{3} & 0\\\\0 & 0 & 0 & 0 & - \\frac{\\sqrt{6}}{6} & \\frac{\\sqrt{6}}{6} & \\frac{\\sqrt{3}}{3} & \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & 0\\\\0 & 0 & 0 & 0 & \\frac{\\sqrt{6}}{6} & - \\frac{\\sqrt{6}}{6} & \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & 0 & \\frac{\\sqrt{3}}{3}\\\\0 & 0 & 0 & 0 & \\frac{\\sqrt{6}}{6} & - \\frac{\\sqrt{6}}{6} & 0 & \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & - \\frac{\\sqrt{3}}{3}\\\\\\frac{\\sqrt{3}}{3} & \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & 0 & 0 & 0 & - \\frac{\\sqrt{3}}{3} & 0 & 0 & 0\\\\0 & 0 & \\frac{\\sqrt{3}}{3} & - \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & 0 & 0 & - \\frac{\\sqrt{3}}{3} & 0 & 0\\\\0 & 0 & 0 & 0 & \\frac{\\sqrt{3}}{3} & \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & 0 & - \\frac{\\sqrt{3}}{3} & 0\\\\0 & 0 & 0 & 0 & 0 & 0 & \\frac{\\sqrt{3}}{3} & - \\frac{\\sqrt{3}}{3} & 0 & 0 & 0 & - \\frac{\\sqrt{3}}{3}\\end{array}\\right]$" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "print(\"The associated matrix\")\n", "pcvl.pdisplay(experiment.unitary_circuit().compute_unitary(use_symbolic=False))" ] }, { "cell_type": "markdown", "id": "7bdc227b", "metadata": {}, "source": [ "### Input state" ] }, { "cell_type": "markdown", "id": "4e1ced97", "metadata": {}, "source": [ "In the preliminaries we provided functions to map qubit states to the corresponding Fock states and vice-versa.\n", "\n", "A *computational basis qubit state* on qubits $x_1, f_1, x_2, f_2$ is represented with a list of 4 boolean values.\n", "\n", "A *Fock state* is represented with a list of twelve integer values.\n", "As described above, for Fock states, the modes are enumerated as follows:\n", "* mode pairs $(1,2), (3,4), (7,8), (9,10)$ respectively correspond to states $0,1$ for qubits $x_1, f_1, x_2, f_2$\n", "* modes $0,5,6,11$ are auxiliary modes used for CZ gates" ] }, { "cell_type": "markdown", "id": "51d9eca7", "metadata": {}, "source": [ "The input state of the circuit is $|0\\rangle_{x_1}|0\\rangle_{x_2}|0\\rangle_{f_1}|1\\rangle_{f_2}$.\n", "With path encoding this corresponds to sending 4 photons in total in the optical circuit, in the input modes corresponding to the initial state of each qubit." ] }, { "cell_type": "code", "execution_count": 5, "id": "79f7ca45", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Input qubit state: |0001>\n", "Corresponding input Fock state |1,0,1,0,1,0,0,1,0,0,0,0>\n" ] } ], "source": [ "qubit_input_state = pcvl.LogicalState([0, 0, 0, 1])\n", "experiment.with_input(qubit_input_state)\n", "input_fock_state = experiment.input_state\n", "\n", "print(f\"Input qubit state: {qubit_input_state}\")\n", "print(f\"Corresponding input Fock state {input_fock_state}\")" ] }, { "cell_type": "markdown", "id": "d3f88c02", "metadata": {}, "source": [ "## Simulation" ] }, { "cell_type": "code", "execution_count": 6, "id": "ca10ff5e", "metadata": {}, "outputs": [], "source": [ "simulator = pcvl.SimulatorFactory.build(experiment, \"SLOS\")" ] }, { "cell_type": "markdown", "id": "e81a4f44", "metadata": {}, "source": [ "### Computing the output state" ] }, { "cell_type": "markdown", "id": "c4e62d76", "metadata": {}, "source": [ "Using perceval we compute the output state obtained with the optical circuit." ] }, { "cell_type": "markdown", "id": "2f3e6f17", "metadata": {}, "source": [ "#### Amplitudes\n", "\n", "We first decompose the output state in the computational basis and plot the corresponding amplitudes." ] }, { "cell_type": "code", "execution_count": 7, "id": "0cd16853", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Output state amplitudes: (post-selected on qubit states, not renormalized)\n", "|x1,f1,x2,f2>\n", "|0010> (0.5+0j)\n", "|1110> (0.5000000000000001+0j)\n", "|0001> (0.49999999999999994+0j)\n", "|1101> (0.5+0j)\n" ] } ], "source": [ "simulator.keep_heralds(False) # Remove ancillary modes in results\n", "evolved_state = simulator.evolve(input_fock_state)\n", "\n", "print(\"Output state amplitudes: (post-selected on qubit states, not renormalized)\")\n", "print(\"|x1,f1,x2,f2>\")\n", "for ouput_fock_state, amplitude in evolved_state:\n", " logical_state = to_logical_state(ouput_fock_state)\n", " print(logical_state, amplitude)" ] }, { "cell_type": "markdown", "id": "43e94f97", "metadata": {}, "source": [ "The amplitudes obtained with perceval correspond to the expected output state\n", "$$\\frac{1}{2} \\left ( |0\\rangle_{x_1}|0\\rangle_{f_1} + |1\\rangle_{x_1}|1\\rangle_{f_1} \\right ) \\otimes \\left ( |0\\rangle_{x_2}|1\\rangle_{f_2} + |1\\rangle_{x_2}|0\\rangle_{f_2} \\right )$$\n", "up to numerical precision, without renormalization." ] }, { "cell_type": "markdown", "id": "300664ac", "metadata": {}, "source": [ "#### Distribution\n", "\n", "We now compute the probabilities to obtain each outcome corresponding to measuring the expected output state in the computational basis." ] }, { "cell_type": "code", "execution_count": 8, "id": "2277ec12", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Output state distribution: (post-selected on expected qubit states, renormalized)\n", "|x1,f1,x2,f2>\n" ] }, { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
|0010> |1110> |0001> |1101>
|0001>1/4 1/4 1/4 1/4
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "input_states = {input_fock_state[:-4]: str(qubit_input_state)}\n", "\n", "expected_output_states = {\n", " output_fock_state: str(to_logical_state(output_fock_state))\n", " for output_fock_state in evolved_state.keys()\n", "}\n", "\n", "p = Processor(\"SLOS\", experiment)\n", "ca = Analyzer(p, input_states, expected_output_states)\n", "ca.compute()\n", "\n", "print(\"Output state distribution: (post-selected on expected qubit states, renormalized)\")\n", "print(\"|x1,f1,x2,f2>\")\n", "pcvl.pdisplay(ca)" ] }, { "cell_type": "markdown", "id": "5e464e2c", "metadata": {}, "source": [ "The distribution computed with Perceval is uniform over each outcome, which corresponds to the expected distribution obtained in the paper when $x_0 = 0$." ] }, { "cell_type": "markdown", "id": "a79723d8", "metadata": {}, "source": [ "### Interpretation of the outcomes\n", "\n", "For each outcome we consider the values of qubits $x_2, x_1, x_0$ (where $x_0$ is 0) which represent a binary number between 0 and 7, here corresponding to 0, 4, 2 and 6 in the order of the table above.\n", "After sampling the circuit, obtaining outcomes 2 or 6 allows to successfully compute the order $r=4$.\n", "Obtaining outcome 0 is an expected failure of the quantum circuit, inherent to Shor's algorithm.\n", "Outcome 4 is an expected failure as well, as it only allows to compute the trivial factors 1 and 15.\n", "\n", "Since the distribution is uniform the circuit successfully yields a successful outcome with probability 1/2. This probability can be amplified exponentially close to $1$ by sampling the circuit multiple times." ] }, { "cell_type": "markdown", "id": "7928b50b", "metadata": {}, "source": [ "## Reference\n", "\n", "> Alberto Politi, Jonathan C.F. Matthews, and Jeremy L. O'brien. \"Shor’s quantum factoring algorithm on a photonic chip.\" [Science](https://www.science.org/doi/10.1126/science.1173731) 325.5945 (2009): 1221-1221." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Simulation_non-unitary_components.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "6f3d5000", "metadata": {}, "source": [ "# Simulation of non-unitary components" ] }, { "cell_type": "markdown", "id": "9147d84a", "metadata": {}, "source": [ "We are interested in simulating a simple circuit with time delay and some loss channels." ] }, { "cell_type": "code", "execution_count": 1, "id": "08fc89a2", "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl" ] }, { "cell_type": "markdown", "id": "dfa6402a", "metadata": {}, "source": [ "## I. Perfect case" ] }, { "cell_type": "markdown", "id": "f0c8997f", "metadata": {}, "source": [ "First, we define a HOM circuit with time delay. Since we are going to use non-unitary components, we will have to use a `Processor`. As we are going to compute all the probabilities, let's use a `SLOS` backend. In a first time, we will use a perfect source with no loss in the circuit." ] }, { "cell_type": "code", "execution_count": 2, "id": "2278ea3e", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "t=1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "HOM = pcvl.Processor(\"SLOS\", 2)\n", "\n", "HOM.add(0, pcvl.BS())\n", "HOM.add(1, pcvl.TD(1))\n", "HOM.add(0, pcvl.BS())\n", "\n", "pcvl.pdisplay(HOM)" ] }, { "cell_type": "markdown", "id": "25fd1b12", "metadata": {}, "source": [ "Then, we define that we will send photons on mode 0. A photon will be sent to the circuit at each time step." ] }, { "cell_type": "code", "execution_count": 3, "id": "228dc3f3", "metadata": {}, "outputs": [], "source": [ "HOM.min_detected_photons_filter(0)\n", "\n", "HOM.with_input(pcvl.BasicState([1, 0]))" ] }, { "cell_type": "markdown", "id": "fa7a020d", "metadata": {}, "source": [ "Before going further, we will try to get an intuition of the result in this simple case.\n", "\n", "Let's focus on a given time step $n$ with $n \\ge 1$.\n", "We will distinguish two cases: \n", "- either the photon of time step $n - 1$ went to mode 0 after the first beam splitter\n", "- either it went to the mode 1 after the first beam splitter.\n", "For both cases, the probability is 1/2 since we have a perfect beam splitter.\n", "The photon of time $n$ can also go to mode 0 or mode 1 with probability 1/2 after the first beam splitter.\n", "\n", "Let's start with the first case.\n", "At time $n$, the photon of time $n - 1$ is no longer in the circuit. Thus it cannot interact with the photon of time $n$.
\n", "If the latter went to mode 0, it will be detected at time $n$ on mode 0 or 1 with a 50/50 probability (since the second beam splitter is perfect too).
\n", "If it went to mode 1, no photon will be detected at time $n$, giving us an empty state.
\n", "This gives us the probability table for this case\n", "\n", "\\|0, 0$\\rangle$ | \\|1, 0$\\rangle$ | \\|0, 1$\\rangle$ \n", "--- | --- | ---\n", "1/2 | 1/4 | 1/4\n", "\n", "Now let's consider the second case.
\n", "If the photon at time $n$ went to mode 1, only the photon of time $n - 1$ will be detected. This will give us a |1, 0$\\rangle$ or a |0, 1$\\rangle$ state with 1/2 probability.
\n", "If the photon at time $n$ went to mode 0, then the second beam splitter will have an input state |1, 1$\\rangle$, which can only give us |2, 0$\\rangle$ and |0, 2$\\rangle$ states with 1/2 probability.
\n", "This gives us the following probability table:\n", "\n", "\\|1, 0$\\rangle$ |\\|0, 1$\\rangle$ |\\|2, 0$\\rangle$ |\\|0, 2$\\rangle$\n", "--- | --- | --- | ---\n", "1/4 | 1/4 | 1/4 | 1/4\n", "\n", "\n", "Finally, we expect, considering each case has a probability of 1/2\n", "\n", "|\\|0, 0$\\rangle$ |\\|1, 0$\\rangle$ |\\|0, 1$\\rangle$ |\\|2, 0$\\rangle$ |\\|0, 2$\\rangle$\n", "--- | --- | --- | --- | ---\n", "1/4 | 1/4 | 1/4 | 1/8 | 1/8" ] }, { "cell_type": "code", "execution_count": 4, "id": "71fdd32e", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|1,0> 1/4
|0,1> 1/4
|0,0> 1/4
|2,0> 1/8
|0,2> 1/8
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "sampler = pcvl.algorithm.Sampler(HOM)\n", "output = sampler.probs()[\"results\"]\n", "\n", "pcvl.pdisplay(output)" ] }, { "cell_type": "markdown", "id": "5076484c", "metadata": {}, "source": [ "This is what we expected." ] }, { "cell_type": "markdown", "id": "f49d9a5b", "metadata": {}, "source": [ "## II. Imperfect case" ] }, { "cell_type": "markdown", "id": "fa01680e", "metadata": {}, "source": [ "Now we are going to add some loss channel to our circuit to simulate an imperfect case. For a first use, we will check that loss channels on all modes are equivalent to source brightness with $loss = 1 - transmission$. We will not do it here, but we can also check that if loss channels are parallel in the circuit (so their result is not going in another loss channel), the computation also gives the same result." ] }, { "cell_type": "code", "execution_count": 5, "id": "d311c666", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "t=1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=1/10\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=1/10\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "imperfect_HOM = pcvl.Processor(\"SLOS\", 2, noise=pcvl.NoiseModel(brightness=.6, g2=.01))\n", "\n", "imperfect_HOM.add(0, HOM)\n", "imperfect_HOM.add(0, pcvl.LC(.1))\n", "imperfect_HOM.add(1, pcvl.LC(.1))\n", "\n", "pcvl.pdisplay(imperfect_HOM)" ] }, { "cell_type": "code", "execution_count": 6, "id": "c0c2ee92", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,0> 0.532246
|0,1> 0.197039
|1,0> 0.197039
|2,0> 0.036545
|0,2> 0.036545
|1,1> 0.000388059
|3,0> 4.94304e-05
|0,3> 4.94304e-05
|2,1> 4.94304e-05
|1,2> 4.94304e-05
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "imperfect_HOM.min_detected_photons_filter(0)\n", "imperfect_HOM.with_input(pcvl.BasicState([1, 0]))\n", "\n", "imperfect_sampler = pcvl.algorithm.Sampler(imperfect_HOM)\n", "output = imperfect_sampler.probs()[\"results\"]\n", "\n", "pcvl.pdisplay(output)" ] }, { "cell_type": "markdown", "id": "243ad3cc", "metadata": {}, "source": [ "Now let's compute this using an imperfect source." ] }, { "cell_type": "code", "execution_count": 7, "id": "0c454029", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,0> 0.532247
|1,0> 0.197038
|0,1> 0.197038
|0,2> 0.036545
|2,0> 0.036545
|1,1> 0.000388116
|0,3> 4.94378e-05
|3,0> 4.94378e-05
|1,2> 4.94378e-05
|2,1> 4.94378e-05
|2,2> 0
|0,4> 0
|4,0> 0
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "HOM.noise = pcvl.NoiseModel(brightness=.6, g2=.01, transmittance=.9)\n", "\n", "# No need to redefine the sampler even if the Processor has changed\n", "output = sampler.probs()[\"results\"]\n", "pcvl.pdisplay(output)" ] }, { "cell_type": "markdown", "id": "17b318fb", "metadata": {}, "source": [ "We obtain the same distribution, which is what is expected." ] }, { "cell_type": "markdown", "id": "a5dca744", "metadata": {}, "source": [ "## III. Playing with processors" ] }, { "cell_type": "markdown", "id": "4848bce2", "metadata": {}, "source": [ "Naturally, loss channels can have different loss values and be represented at several places of the circuit to represent each component loss, or detectors loss." ] }, { "cell_type": "code", "execution_count": 8, "id": "004d10c8", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=1/50\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=1/50\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "t=1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=0.01\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=1/50\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=1/50\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=0.03\n", "\n", "\n", "\n", "0\n", "1\n", "0\n", "1\n", "" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "BS_loss = .02\n", "TD_loss = .01\n", "\n", "# Here, the backend name have no importance, as only the backend of the global processor will be used\n", "lossy_BS = (pcvl.Processor(\"SLOS\", 2)\n", " .add(0, pcvl.BS())\n", " .add(0, pcvl.LC(BS_loss))\n", " .add(1, pcvl.LC(BS_loss))\n", " )\n", "\n", "lossy_TD = (pcvl.Processor(\"SLOS\", 1)\n", " .add(0, pcvl.TD(1))\n", " .add(0, pcvl.LC(TD_loss))\n", " )\n", "\n", "lossy_HOM = (pcvl.Processor(\"SLOS\", 2)\n", " .add(0, lossy_BS)\n", " .add(1, lossy_TD)\n", " .add(0, lossy_BS)\n", " .add(0, pcvl.LC(.03)) # Lossy detector on mode 0\n", " )\n", "\n", "pcvl.pdisplay(lossy_HOM)" ] }, { "cell_type": "code", "execution_count": 9, "id": "ccdb64f9", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,0> 0.280276
|0,1> 0.249513
|1,0> 0.248671
|0,2> 0.114143
|2,0> 0.107397
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "lossy_HOM.min_detected_photons_filter(0)\n", "lossy_HOM.with_input(pcvl.BasicState([1, 0]))\n", "\n", "lossy_sampler = pcvl.algorithm.Sampler(lossy_HOM)\n", "output = lossy_sampler.probs()[\"results\"]\n", "\n", "pcvl.pdisplay(output)" ] }, { "cell_type": "markdown", "id": "61921f94", "metadata": {}, "source": [ "We can see that the lossy detector gave us non-symmetrical results, with less probability to detect something on mode 0. An interesting fact is that the loss on the time delay does not alter the symmetry of the probabilities, because it is followed by a perfect beam splitter." ] }, { "cell_type": "markdown", "id": "4e6feedb", "metadata": {}, "source": [ "All functions of the processor remain available. For example, we can add some heralding. Let's take a simpler processor than in the previous case, to make results comparable.\n", "\n", "First, here is a reminder of the probabilities of the imperfect HOM with a loss of 0.1 on each mode." ] }, { "cell_type": "code", "execution_count": 10, "id": "719970e6", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,0> 0.532246
|0,1> 0.197039
|1,0> 0.197039
|2,0> 0.036545
|0,2> 0.036545
|1,1> 0.000388059
|3,0> 4.94304e-05
|0,3> 4.94304e-05
|2,1> 4.94304e-05
|1,2> 4.94304e-05
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "output = imperfect_sampler.probs()[\"results\"]\n", "\n", "pcvl.pdisplay(output)" ] }, { "cell_type": "markdown", "id": "61e168b8", "metadata": {}, "source": [ "Now we add a 0 herald on mode 1, and we will check that the results are correct." ] }, { "cell_type": "code", "execution_count": 11, "id": "a0c398a4", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "t=1\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=1/10\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "loss=1/10\n", "\n", "\n", "[herald0]\n", "0\n", "\n", "\n", "1\n", "\n", "\n", "[herald0]\n", "0\n", "0\n", "0\n", "" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "imperfect_HOM.add_herald(1, 0)\n", "pcvl.pdisplay(imperfect_HOM)" ] }, { "cell_type": "code", "execution_count": 12, "id": "5a5b656e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "expected performance 0.76587973916485\n" ] }, { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0> 0.694948
|1> 0.257271
|2> 0.047717
|3> 6.45407e-05
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "expected = pcvl.BSDistribution()\n", "expected_perf = 1\n", "for state, prob in output.items():\n", " if state[1] == 0:\n", " expected[state[:1]] = prob\n", " else:\n", " expected_perf -= prob\n", "\n", "print(\"expected performance\", expected_perf)\n", "# expected is not normalized here, but its display is\n", "pcvl.pdisplay(expected)" ] }, { "cell_type": "code", "execution_count": 13, "id": "445c4a1c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "performance 0.765877571708347\n" ] }, { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0> 0.694948
|1> 0.257271
|2> 0.047717
|3> 6.45407e-05
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "output = imperfect_sampler.probs()\n", "\n", "print(\"performance\", output[\"global_perf\"])\n", "pcvl.pdisplay(output[\"results\"])" ] }, { "cell_type": "markdown", "id": "5b20d839", "metadata": {}, "source": [ "We obtain the same result in both cases." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/State_Tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "7f6effb781342585", "metadata": {}, "source": [ "# Quantum States" ] }, { "cell_type": "markdown", "id": "b0357168bef55424", "metadata": {}, "source": [ "This tutorial shows how to handle quantum states in Perceval.\n", "\n", "First, the necessary import:" ] }, { "cell_type": "code", "execution_count": 1, "id": "1846601e344992c4", "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "# or you can import each symbol, depending on your prefered coding style\n", "from perceval import BasicState, StateVector, SVDistribution, BSDistribution, BSCount, BSSamples" ] }, { "cell_type": "markdown", "id": "2fae428413e5493c", "metadata": {}, "source": [ "## I. BasicState\n", "\n", "In linear optics (LO) Circuits, photons can have many discrete degrees of freedom, called modes.\n", "It can be the frequency, the polarisation, the position, or all of them.\n", "\n", "We represent these degrees of freedom with Fock states. If we have $n$ photons over $m$ modes, the Fock state $|s_1,s_2,...,s_m\\rangle$ means we have $s_i$ photons in the $i^{th}$ mode. Note that $\\sum_{i=1}^m s_i =n$.\n", "\n", "

\n", "Modes are using [0-based numbering](https://en.wikipedia.org/wiki/Zero-based_numbering) - so mode 0 is\n", "corresponding to the first one, and mode $(m-1)$ is corresponding to the $m$-th.

\n", "\n", "In Perceval, we will use the class `pcvl.BasicState`." ] }, { "cell_type": "code", "execution_count": 2, "id": "initial_id", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "|0,2,0,1>\n", "Number of modes: 4\n", "Number of photons: 3\n", "bs1 and bs2 are the same states\n", "There is 0 photon in mode 0 (or equivalently bs1[0]=0).\n", "There is 2 photon in mode 1 (or equivalently bs1[1]=2).\n", "There is 0 photon in mode 2 (or equivalently bs1[2]=0).\n", "There is 1 photon in mode 3 (or equivalently bs1[3]=1).\n" ] } ], "source": [ "## Syntax of different BasicState (list, string, etc)\n", "bs1 = BasicState([0, 2, 0, 1])\n", "bs2 = BasicState('|0,2,0,1>') # Must start with | and end with >\n", "\n", "print(bs1)\n", "print(f\"Number of modes: {bs1.m}\")\n", "print(f\"Number of photons: {bs1.n}\")\n", "\n", "if bs1 == bs2:\n", " print(\"bs1 and bs2 are the same states\")\n", "\n", "## You can iterate on modes\n", "for i, photon_count in enumerate(bs1):\n", " print(f\"There is {photon_count} photon in mode {i} (or equivalently bs1[{i}]={bs1[i]}).\")" ] }, { "cell_type": "markdown", "id": "af7322010b167d02", "metadata": {}, "source": [ "A `BasicState` is actually more than just a Fock state representation." ] }, { "cell_type": "code", "execution_count": 3, "id": "9b324ca9d34528a4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " True\n", " True\n", " True\n" ] } ], "source": [ "# There are three kind of BasicStates\n", "\n", "photon_position = [0, 2, 0, 1]\n", "\n", "# FockState, all photons are indistinguishable\n", "bs1 = BasicState(photon_position) # Or equivalently BasicState(\"|0,2,0,1>\")\n", "print(type(bs1), isinstance(bs1, BasicState))\n", "\n", "# NoisyFockState, photons with the same tag are indistinguishable, photons with different tags are distinguishable (they will not interact)\n", "noise_index = [0, 1, 0]\n", "bs2 = BasicState(photon_position, noise_index) # Or equivalently BasicState(\"|0,{0}{1},0,{0}>\")\n", "print(type(bs2), isinstance(bs2, BasicState))\n", "\n", "# AnnotatedFockState, with custom annotations (not simulable in the general case, needs a conversion to something simulable first)\n", "bs3 = BasicState(\"|0,{lambda:925}{lambda:925.01},0,{lambda:925.02}>\")\n", "print(type(bs3), isinstance(bs3, BasicState))\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "cd65ad3ce682196b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Reminder, bs1 = |0,2,0,1>\n", "bs1 * |1,2> = |0,2,0,1,1,2>\n", "A slice of bs1 (extract modes #1 & 2) = |2,0>\n", "bs1 with threshold detection applied = |0,1,0,1>\n" ] } ], "source": [ "# Basic methods are common between these types\n", "print(\"Reminder, bs1 =\", bs1)\n", "print(\"bs1 * |1,2> =\", bs1 * BasicState([1, 2])) # Tensor product\n", "print(\"A slice of bs1 (extract modes #1 & 2) =\", bs1[1:3]) # Slice\n", "print(\"bs1 with threshold detection applied =\", bs1.threshold_detection()) # Apply a threshold detection to the state, limiting detected photons to 1 per mode" ] }, { "cell_type": "markdown", "id": "ec59dd21de3157ba", "metadata": {}, "source": [ "## II. StateVector\n", "\n", "A `StateVector` is a complex superposition of `BasicState` with the same number of modes. It can represent any pure state in the Fock space" ] }, { "cell_type": "code", "execution_count": 5, "id": "8575795407f7140a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "State vector is normalized upon use and display: (0.432+0.259I)*|1,0,1,1>+0.864*|0,2,0,1>\n", "|1,0,1,1> has the complex amplitude (0.4319342127906801+0.25916052767440806j)\n", "|0,2,0,1> has the complex amplitude (0.8638684255813602+0j)\n", "(0.8638684255813602+0j)\n" ] } ], "source": [ "# StateVectors can be defined using arithmetic on BasicStates and other StateVectors\n", "sv = (0.5 + 0.3j) * BasicState([1, 0, 1, 1]) + BasicState([0, 2, 0, 1])\n", "\n", "# State vectors normalize themselves upon use\n", "print(\"State vector is normalized upon use and display: \", sv)\n", "\n", "for (basic_state, amplitude) in sv:\n", " print(basic_state, \"has the complex amplitude\", amplitude)\n", "\n", "# We can also access amplitudes as in a dictionary\n", "print(sv[pcvl.BasicState([0, 2, 0, 1])])" ] }, { "cell_type": "markdown", "id": "452ef27cc289356e", "metadata": {}, "source": [ "## III. Distributions\n", "\n", "Perceval contains a state vector distribution class that embodies a mixed state. All states defined in this distribution must have the same number of modes." ] }, { "cell_type": "code", "execution_count": 6, "id": "fa17c00336024203", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Five random samples according to the distribution: [0.707*|3,0>+0.707*|2,1>, 0.707*|3,0>+0.707*|2,1>, 0.707*|3,0>+0.707*|2,1>, 0.707*|3,0>+0.707*|2,1>, |1,2>]\n", "{\n", "\t|0>: 0.3333333333333333\n", "\t0.707*|1>+0.707*|2>: 0.6666666666666666\n", "} sum of probabilities 1.0\n", "Tensor product\n", "{\n", "\t0.707*|1,2,1>+0.707*|1,2,2>: 0.26666666666666666\n", "\t|1,2,0>: 0.13333333333333333\n", "\t0.707*|3,0,0>+0.707*|2,1,0>: 0.19999999999999998\n", "\t0.5*|3,0,1>+0.5*|3,0,2>+0.5*|2,1,1>+0.5*|2,1,2>: 0.39999999999999997\n", "}\n" ] } ], "source": [ "# A SVDistribution is a collection of StateVectors\n", "svd = SVDistribution({StateVector([1, 2]) : 0.4,\n", " BasicState([3, 0]) + BasicState([2, 1]) : 0.6})\n", "\n", "print(\"Five random samples according to the distribution:\", svd.sample(5))\n", "\n", "svd2 = SVDistribution({StateVector([0]) : 0.1,\n", " BasicState([1]) + BasicState([2]) : 0.2})\n", "svd2.normalize() # distributions have to be normalized to make sense\n", "print(svd2, \"sum of probabilities\", sum(svd2.values()))\n", "\n", "print(\"Tensor product\")\n", "print(svd * svd2) # Tensor product between distributions" ] }, { "cell_type": "markdown", "id": "4410cc950d933f0", "metadata": {}, "source": [ "## IV. Results\n", "\n", "When running a Perceval computation, you can expect the result type from the input and the command:\n", "\n", "Performing a state evolution returns\n", "* A `StateVector` from a pure state input\n", "* A `SVDistribution` from a mixed state input\n", "\n", "Most other Perceval computations return *measurements*. Three types exist to match the following commands:\n", "* `probs` (for \"probabilities\") returns a `BSDistribution`\n", "* `sample_count` returns a `BSCount`\n", "* `samples` returns a `BSSamples`\n", "\n", "These data structures only hold simple `FockState` instances without any noise index or annotation, as these data do not survive a measurement." ] }, { "cell_type": "code", "execution_count": 7, "id": "4c09f45da57f6f1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", "\t|1,2>: 0.4\n", "\t|2,1>: 0.2\n", "}\n", "Number of modes: 2\n", "|1,2> has the probability 0.6666666666666666 (or equivalently 0.6666666666666666)\n", "|2,1> has the probability 0.3333333333333333 (or equivalently 0.3333333333333333)\n", "Tensor product\n", "{\n", "\t|1,2,1>: 0.6666666666666666\n", "\t|2,1,1>: 0.3333333333333333\n", "}\n", "{\n", " |1,2>: 20\n", " |2,1>: 118\n", "}\n", "Total number of samples: 138\n" ] } ], "source": [ "# The BSDistribution is a collection of FockStates\n", "bsd = BSDistribution()\n", "bsd[BasicState([1, 2])] = 0.4\n", "bsd[BasicState([2, 1])] = 0.2\n", "\n", "print(bsd)\n", "print(\"Number of modes:\", bsd.m)\n", "\n", "bsd.normalize() # Not automatic on distributions\n", "\n", "# Distributions act much like python dictionaries\n", "for (state, probability) in bsd.items():\n", " print(state, \"has the probability\", probability, f\"(or equivalently {bsd[state]})\")\n", "\n", "print(\"Tensor product\")\n", "print(bsd * BasicState([1])) # Tensor product, also works between distributions\n", "\n", "bsc = BSCount()\n", "bsc[BasicState([1, 2])] = 20\n", "bsc[BasicState([2, 1])] = 118\n", "print(bsc)\n", "print(\"Total number of samples:\", bsc.total())" ] }, { "cell_type": "markdown", "id": "c763b109e9e24543", "metadata": {}, "source": [ "Functions exist to convert these measurement results from one type to another" ] }, { "cell_type": "code", "execution_count": 8, "id": "777dc447adf6bde2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "bsd converted to a BSCount = {\n", " |1,2>: 10\n", " |2,1>: 10\n", "}\n", "bsc converted to a BSDistribution = {\n", "\t|1,2>: 0.14492753623188406\n", "\t|2,1>: 0.855072463768116\n", "}\n", "bsc converted to a BSSamples = [ |2,1>, |2,1>, |2,1>, |2,1>, |2,1>, |2,1>, |2,1>, |1,2>, |2,1>, |1,2>, |2,1>, ... (size=138) ]\n" ] } ], "source": [ "from perceval import probs_to_sample_count, sample_count_to_probs, sample_count_to_samples\n", "\n", "print(\"bsd converted to a BSCount =\", probs_to_sample_count(bsd, max_samples=20))\n", "print(\"bsc converted to a BSDistribution =\", sample_count_to_probs(bsc))\n", "print(\"bsc converted to a BSSamples =\", sample_count_to_samples(bsc)) # Here the sample order is generated by a Python random library" ] }, { "cell_type": "markdown", "id": "505a479e680b89b1", "metadata": {}, "source": [ "Now that you know how to define states in perceval, you are ready to go to the next part of the tutorial about LO circuits." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Tomography_walkthrough.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "# Tomography of a CNOT Gate" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "This notebook is directed to explain how a Quantum Process Tomography experiment can be performed in Perceval. 2 qubit Heralded CNOT gate (created using the Knill CNOT) is used as the *target* gate operation under study to demonstrate the experiment and the results." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## I. Quantum Process Tomography\n", "Quantum Process Tomography (QPT) is a tool to reconstruct the mathematical operation associated to a physical gate by a series of different measurements on different inputs. QPT scales exponentially with the number of qubits. Any quantum channel (completely positive quantum operation) has a $\\chi$ matrix representation : $$\\varepsilon(\\rho)=\\sum_{m,n}\\chi_{mn}E_m\\rho E_n^†$$QPT reconstructs the $\\chi$ matrix.\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import perceval as pcvl\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "from perceval.utils import NoiseModel\n", "from perceval.components import catalog, Processor, BS\n", "from perceval.algorithm import ProcessTomography\n", "from perceval.algorithm.tomography import is_physical, process_fidelity\n", "from perceval.algorithm import ProcessTomographyMLE, StateTomographyMLE\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### In Perceval\n", "\n", "*ProcessTomography* requires a Perceval *Processor* as input which includes the *Source* and the *Gate* or *Operation Circuit* under study. Note that any source imperfections needs to be included in the Processor. Any heralding and/or any post-selection should also be defined within the *Processor* (see *Processor* for more information)\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "#### The Heralded (Knill) CNOT gate" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "HERALDED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=pi\n", "\n", "\n", "Φ=pi\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=10.655737\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=0.61548\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "1\n", "\n", "[herald1]\n", "1\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "1\n", "\n", "[herald1]\n", "1\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cnot = catalog[\"heralded cnot\"].build_processor()\n", "pcvl.pdisplay(cnot, recursive=True)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "Instantiating a *ProcessTomography* object with Knill CNOT gate from Perceval's catalog with a perfect source and computing the $\\chi$ matrix for the gate operation." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "qpt = ProcessTomography(operator_processor=cnot)\n", "chi_op = qpt.chi_matrix() # computing the chi matrix\n", "pcvl.pdisplay(qpt, render_size=(15,30)) # visualization of the same chi" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## II. Process Fidelity Computations\n", "\n", "Once having the χ matrix, it might be useful to know how close the heralded gate is to the real CNOT operation. We can compute the process fidelity $$F_{\\chi}= Tr({\\chi}_{ideal} {\\chi}_{physical})$$" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Process Fidelity of CNOT gate operation 1.0000000000000009\n" ] } ], "source": [ "# checking fidelity\n", "op_CX = np.array([[1, 0, 0, 0],\n", " [0, 1, 0, 0],\n", " [0, 0, 0, 1],\n", " [0, 0, 1, 0]], dtype=np.cdouble)\n", "\n", "chi_op_ideal = qpt.chi_target(op_CX)\n", "cnot_fidelity = process_fidelity(chi_op, chi_op_ideal)\n", "print(\"Process Fidelity of CNOT gate operation\", cnot_fidelity)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "As expected, the fidelity is one. The gate behaves as designed in the ideal case." ] }, { "cell_type": "markdown", "metadata": { "ExecuteTime": { "end_time": "2023-11-10T15:04:01.749081Z", "start_time": "2023-11-10T15:04:01.727851Z" }, "collapsed": false }, "source": [ "## III. Average fidelity\n", "Computing the process fidelity can be quite long because we are manipulating matrices that scale exponentially with the number of qubits. If we are only interested in the fidelity of the gate, we can compute its average fidelity : $$\\bar{F}= \\frac{1}{d+1}+\\frac{1}{d^2(d+1)}\\sum_j Tr(U E_j^† U^† \\varepsilon(E_j))$$" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "average fidelity : 0.9999999999999993\n" ] } ], "source": [ "f_avg = qpt.average_fidelity(op_CX)\n", "print(\"average fidelity :\",f_avg)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## IV. Physicality of the maps\n", "\n", "When we reconstruct a process map, an important question is to know whether it is physical or not. A map is called physical if it is trace-preserving (TP), Hermitian, and completely positive (CP).\n", "\n", "A map is TP if its $\\chi$ matrix is trace 1. To check the CP part is a bit more difficult: The general algorithm (for finite dimensions is using the Choi-Jamiolkowski-isomorphism).\n", "\n", "A map $\\epsilon$ is CP iff the Choi matrix\n", "$$S:=(\\varepsilon \\otimes I_d)(\\ket{\\Omega}\\bra{\\Omega})$$\n", "is a positive semidefinite matrix, where ∣Ω⟩=∑i=1d​∣ii⟩ is the maximally entangled state." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Results from testing the physicality of the Chi matrix for the CNOT gate operation\n", "{'Trace=1': True, 'Hermitian': True, 'Completely Positive': True}\n" ] } ], "source": [ "print('Results from testing the physicality of the Chi matrix for the CNOT gate operation')\n", "print(is_physical(chi_op, 2))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "It must be mentioned that the $\\chi$ computed by tomography is normalized by the gate efficiency to be a trace preserving physical map. The user can, however, ask for the un-normalized $\\chi$ and the gate efficiency if needed." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Un normalized Chi matrix is not physical: not Trace preserving\n", "{'Trace=1': False, 'Hermitian': True, 'Completely Positive': True}\n", "Gate efficiency of the Knill CNOT implemented is = (0.07407407407407408-9.33833350393588e-20j)\n" ] } ], "source": [ "chi_raw = qpt.chi_unnormalized\n", "gate_eff = qpt.gate_efficiency\n", "print(\"Un normalized Chi matrix is not physical: not Trace preserving\")\n", "print(is_physical(chi_raw, 2))\n", "print(\"Gate efficiency of the Knill CNOT implemented is =\", gate_eff)" ] }, { "cell_type": "markdown", "metadata": { "ExecuteTime": { "end_time": "2023-11-10T15:13:25.676465Z", "start_time": "2023-11-10T15:13:25.663909Z" }, "collapsed": false }, "source": [ "## V. Error Process Map\n", "\n", "A nice way to highlight the errors in the computation is to look at the error process map. $$\\chi_{err}=V \\chi V^†$$\n", "\n", "$$V_{mn} = \\frac{Tr(E_m^† E_n U^†)}{d}$$\n", "\n", "The error map is computed in the following cell by incorporating source imperfections to the CNOT gate processor." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "image/png": "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xFU1Q588edJgoF+HDh3w0ksv4aWXXsKpU6cQGhqKd955B5988on0F/aJEyf0mvkrKyuRl5dX44+DboDsnbM2qmoJqC4R6NChA3bu3InevXvL/pEZNWoUnn/+eZw4cQIbNmxAkyZNMHToUOn5X375BSdPnsSaNWswduxYqfz2mR/VMfYYvby84ObmBo1GY9QPqqurK0aNGoVRo0ahsrISw4cPx7x58zBjxoxqpx4HBATg559/hlar1fsx0s02ubO1RGleXl5o0qQJTpw4YfDc8ePHYWdnB39/fwUig9Gf+w4dOgAAjh07Vu37qtve0dHRqPe+RYsWSExMRGJiIsrKytC3b1/MmTNHSmB0+63unDXWk08+iVmzZuE///lPlYPndTZt2oT27dvj888/1ztPq7t8g7HfO9Q4sAupkfvnn3/w+eefY8iQIRgxYoTBkpycjNLSUoMpmVu2bJH61gHg8OHDOHToEOLi4gAA165dw/Xr1/W26dChA9zc3FBRUQEAiImJgZOTE9577z0IIaT1/v3vf6O4uLjKWQs6AQEBsLe3x759+/TKly5darCu7robdyYCI0eOhEajwWuvvWawzc2bN2udyg3cmrVhb2+PTz/9FBkZGRgyZIjedT50Yx9uPz4hhME02Kq4u7vD09Oz1mO0t7fHo48+is8++6zKKaWXLl2S/v/XX3/pPefk5ISgoCAIIXDjxo1qYxk0aBAKCgqk7jLg1mu0ePFiNG3aVOoSayjs7e0xcOBAfPHFF3rdC4WFhVi3bh369Omj181Xn4z93N97771o164d0tLSDD6Luu28vb0RHR2NFStW4D//+Y/Bvmp675s2bYqOHTtK56Mx56yxOnTogLS0NKSmpiIyMrLa9ao6Pw4dOoQDBw5UuX5t3zvUuLAFppH78ssvUVpaioceeqjK5++77z54eXlh7dq1GDVqlFTesWNH9OnTB8899xwqKiqQlpaGli1bSmMATp48iQEDBmDkyJEICgqCg4MDNm/ejMLCQjz++OMAbv2VPGPGDMydOxcPPvggHnroIZw4cQJLly5FREREjRc38/DwwGOPPYbFixdDpVKhQ4cO2Lp1a5XjVsLCwgAAEydORGxsLOzt7fH444+jX79+ePbZZ5Gamorc3FwMHDgQjo6OOHXqFDIyMrBo0SKMGDGixtfP29sb/fv3x8KFC1FaWqr3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bGwsXF1d4efnh1dffRV33rrs9jEw//zzDzp37ozOnTvrdeVeuXIFrVq1Qq9evaDRaMyOT+55df78eQwbNgyurq7w9vbGiy++WO37fejQITz44IPw8PBAkyZN0K9fP/zwww9668yZMwcqlQqnT59GQkICmjVrBg8PDyQmJuLatWt66xrzXRQYGIhff/0Ve/fuld7P28eZXL16FZMnT4a/vz/UajU6duyI+fPnQ6vV1njcQ4YMQfv27at8rmfPnggPD5ceyz2nVq9eXWW3RXVjjIx5fUtLSzF58mQEBgZCrVbD29sbDzzwAHJycqqN4+eff4ZKpcKXX34plWVnZ0OlUuHee+/VWzcuLg49evQwqOP7779HZGQknJ2d0b59e3z00UcG6xjzXtT2+3D8+HGMGDECLVq0gLOzM8LDw/XiVlpJSQl27NiBp556SkpeAGDs2LFo2rQpNm7cqGB0DYQwQevWrUX79u2NXj8+Pl4AECNGjBDp6eli7NixAoAYNmyY3nr9+vUT/fr1kx5fu3ZNdOnSRTg6OooXX3xRvPfeeyIqKkoAEGlpadJ6eXl5AoAICgoS7du3F2+++aZ49913xblz58TKlSulfa9YsUIsWrRIPP3002LixIk1xmzqvu+55x7RsWNHMX/+fPHWW28JT09P0aZNG1FZWVnjfnbv3i0AiN27d0tlH374oQAgIiIixLvvviumT58uXFxcRGBgoPj777+l9bZu3SpUKpXo3r27WLhwoZg5c6Zo3ry56NatmwgICKhxv4WFhaJ58+birrvuEm+//bZ4//33xf/93/+JLl266K2XkJAgAIi4uDiRlpYmFixYIB5++GGxePFiaZ2IiAiRkJAg3n33XbF48WIxcOBAAUAsWbJEr64731+NRiMGDhwomjRpIiZPnixWrFghkpOThYODg3j44YdrjL863bt3F48++qj0ePPmzcLOzk4AEMeOHZPKu3btKkaMGKEXm5+fn/D39xeTJk0SS5cuFffff78AIL7++mtpPd37/eGHHwohhPjxxx/FAw88IACIjz/+WFp0xo8fLxwcHMSECRPE8uXLxcsvvyxcXV1FRERErZ+N+Ph44ezsLDp16iTGjBkjlixZIoYMGSIAiJkzZ+qtC0DMnj1benzw4EFhb28vXnzxRans8ccfFy4uLuLEiRMmx3fne2fOeXXXXXcJZ2dn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eVpSUoJDhw416PMU+F/ycurUKezcuRMtW7ZUOqRajRkzBj///LPeeern54epU6fi22+/VTo8skHsQpIpJSUF8fHxCA8PR2RkJNLS0lBeXo7ExESlQ6tWUlIS1q1bhy+++AJubm7SOAAPDw+4uLgoHF313NzcDMbpuLq6omXLlg16/M6LL76IXr164Y033sDIkSNx+PBhrFy5EitXrlQ6tFoNHToU8+bNQ9u2bdG1a1f89NNPWLhwIcaNG6d0aHrKyspw+vRp6XFeXh5yc3PRokULtG3bFpMnT8brr7+OTp06oV27dpg5cyb8/PwwbNgw5YJGzXG3atUKI0aMQE5ODrZu3QqNRiOdqy1atICTk5NSYdf6et+ZaDk6OsLX1xd33313fYdKjYHS06Cs2eLFi0Xbtm2Fk5OTiIyMFAcPHlQ6pBoBqHL58MMPlQ7NZNYwjVoIIb766ivRrVs3oVarRefOncXKlSuVDskoJSUlYtKkSaJt27bC2dlZtG/fXvzf//2fqKioUDo0Pbt3767yMx0fHy+EuDWVeubMmcLHx0eo1WoxYMAAceLECWWDFjXHnZeXV+25unv37gYbd1U4jZrqkkqIBnZpTSIiIqJacAwMERERWR0mMERERGR1mMAQERGR1WECQ0RERFaHCQwRERFZHSYwREREZHWYwBAREZHVYQJDREREVocJDBEREVkdJjBERERkdZjAEBERkdVhAkNERERW5/8DRdElMp1U7lEAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "The upper left coefficient of the error process map (0.9459712095770586-4.166763721283864e-17j)\n" ] } ], "source": [ "# Error map for an operation with an imperfect source with properties g2=0.00732, indistinguishability=0.9438\n", "noise = NoiseModel(g2=0.00732, indistinguishability=0.9438)\n", "cnot_imperfect_src = catalog[\"heralded cnot\"].build_processor()\n", "cnot_imperfect_src.noise = noise\n", "\n", "qpt_imperfect_source = ProcessTomography(nqubit=2, operator_processor=cnot_imperfect_src)\n", "chi_op_imperfect_src = qpt_imperfect_source.chi_matrix() # computing the chi matrix\n", "\n", "U=qpt_imperfect_source.error_process_matrix(chi_op_imperfect_src, op_CX)\n", "\n", "# many values too small in U, filtering them out\n", "U = np.where(abs(U)<1e-6, 0, U)\n", "\n", "plt.imshow((abs(U)), norm='logit')\n", "plt.suptitle('Absolute values of Error Process Map')\n", "plt.title('Colors on log scale, white pixels denote values where U=0')\n", "plt.colorbar()\n", "plt.show()\n", "\n", "print('The upper left coefficient of the error process map', U[0,0])" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "The upper left coefficient is the process fidelity while all the other non-zero coefficients represent some kind of errors. For example, non-zero imaginary coefficients on the first row (or first column) represent first-order unitary errors. Non-zero diagonal coefficients represent Pauli errors." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "## VI. Maximum Likelihood Estimation (MLE) for Tomography" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "An interesting question is to explore techniques to generate completely positive maps. Perceval features an implementations of maximum likelihood estimation for State and Process Tomography." ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "State Tomography using MLE is demonstrated by creating a processor that generates a GHZ state." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Fidelity of the GHZ state reconstruction : 1.0000000000000009\n", "Trace of the reconstructed density matrix : (1+0j)\n" ] } ], "source": [ "GHZ_TARGET = np.zeros((8, 8))\n", "GHZ_TARGET[0, 0], GHZ_TARGET[0, -1], GHZ_TARGET[-1, 0], GHZ_TARGET[-1, -1] = 1, 1, 1, 1\n", "GHZ_TARGET /= 2\n", "\n", "# Creating a processor to generate GHZ state\n", "ghz_state_proc = Processor(\"SLOS\", 6)\n", "ghz_state_proc.add(0, BS.H())\n", "ghz_state_proc.add(0, cnot)\n", "ghz_state_proc.add(2, cnot)\n", "\n", "# performing state tomography on the processor to reconstruct GHZ state\n", "s_mle = StateTomographyMLE(ghz_state_proc)\n", "ghz_state = s_mle.state_tomography_density_matrix()\n", "\n", "fidelity = s_mle.state_fidelity(GHZ_TARGET, ghz_state)\n", "\n", "print(\"Fidelity of the GHZ state reconstruction :\", fidelity)\n", "print(\"Trace of the reconstructed density matrix :\", np.trace(ghz_state))" ] }, { "cell_type": "markdown", "metadata": { "collapsed": false }, "source": [ "The Knill CNOT gate, previously used in process tomography, is utilized here to illustrate how the MLE technique reconstructs a physical process map.\n", "\n", "Remark: The construction of $\\chi$ matrix here does not include normalization by the gate efficiency which had produced an unphysical map earlier with un-normalised $\\chi$." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "# Performing process tomography using MLE\n", "qpt_mle = ProcessTomographyMLE(operator_processor=cnot)\n", "chi_cnot_mle = qpt_mle.chi_matrix()\n", "\n", "chi_cnot_mle_ideal = qpt_mle.chi_target(op_CX)\n", "cnot_mle_fidelity = process_fidelity(chi_cnot_mle, chi_cnot_mle_ideal)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The fidelity of process tomography MLE to reconstruct the process map : 1.0\n", "Is this process physical {'Trace=1': True, 'Hermitian': True, 'Completely Positive': True}\n" ] } ], "source": [ "print('The fidelity of process tomography MLE to reconstruct the process map :', cnot_mle_fidelity)\n", "print('Is this process physical', is_physical(chi_cnot_mle, 2))" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 } ================================================ FILE: docs/source/notebooks/Two-particle_bosonic-fermionic_quantum_walk.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Two-particle bosonic-fermionic quantum walk\n", "We provide an implementation of the two-particle quantum walk. The aim is to reproduce the results of \"Two-particle bosonic-fermionic quantum walk via integrated photonics\" by L. Sansoni et al. [[1]] with Perceval.\n", "\n", "[1]: https://arxiv.org/pdf/1106.5713.pdf" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# imports\n", "import matplotlib.pyplot as plt\n", "\n", "import numpy as np\n", "\n", "import perceval as pcvl\n", "from perceval.components.unitary_components import BS\n", "from perceval.backends import SLOSBackend\n", "from perceval.simulators import Simulator\n", "from perceval.components import Source\n", "\n", "## Use the symbolic skin for display\n", "from perceval.rendering import DisplayConfig, SymbSkin\n", "DisplayConfig.select_skin(SymbSkin)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Building an array of beam splitters \n", "The dynamics of a quantum walk can be achieved by an array of beam splitters (BSs) as in figure. Here we reproduce a four steps quantum walk, we highlight the difference between the optical spatial modes (in red) and the walk positions (in blue)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"Quantum" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# number of steps\n", "steps = 4\n", "# spatial modes are twice the number of steps\n", "n = 2*steps\n", "\n", "# BS_array contains the input modes of the BSs at each step\n", "BS_array = [[[0]*2]*(i+1) for i in range(steps)]\n", "\n", "i_0 = n/2\n", "for s in range(steps):\n", " if s==0:\n", " BS_array[s][0] = [i_0, i_0-1]\n", " else:\n", " z = 0\n", " for i, j in BS_array[s-1]:\n", " if [i+1, i] not in BS_array[s]:\n", " BS_array[s][z] = [i+1, i]\n", " z += 1\n", " if [j, j-1] not in BS_array[s]:\n", " BS_array[s][z] = [j, j-1]\n", " z += 1\n", "\n", "# build the circuit\n", "circuit = pcvl.Circuit(n)\n", "for s in range(steps):\n", " for bs in BS_array[s]:\n", " circuit.add(int(bs[1]), BS())\n", "\n", "# display the circuit\n", "pcvl.pdisplay(circuit)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Single photon quantum walk\n", "We can check the functioning of the BSs array as a quantum walk simulator putting a single photon in the first input position (mode 3 <-> walk position 0) of the array. Then we can check the output probability distribution of the photon in the corresponding walk positions." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "output distribution: {\n", "\t|0,0,1,0,0,0,0,0>: 0.0625\n", "\t|1,0,0,0,0,0,0,0>: 0.0625\n", "\t|0,1,0,0,0,0,0,0>: 0.0625\n", "\t|0,0,0,0,0,1,0,0>: 0.5625000000000001\n", "\t|0,0,0,1,0,0,0,0>: 0.0625\n", "\t|0,0,0,0,1,0,0,0>: 0.0625\n", "\t|0,0,0,0,0,0,0,1>: 0.0625\n", "\t|0,0,0,0,0,0,1,0>: 0.0625\n", "}\n" ] } ], "source": [ "# define input state by inserting a photon in the first mode\n", "mode = 3\n", "in_list = [0]*n\n", "in_list[mode] = 1\n", "in_state = pcvl.BasicState(in_list)\n", "\n", "# select a backend and define the simulator on the circuit\n", "simulator = Simulator(SLOSBackend())\n", "simulator.set_circuit(circuit)\n", "\n", "#Define a source and input distribution due to source noise\n", "source = Source(losses=0, indistinguishability=1)\n", "input_distribution = source.generate_distribution(expected_input=in_state)\n", "\n", "prob_dist = simulator.probs_svd(input_distribution)\n", "print(\"output distribution:\", prob_dist[\"results\"])\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From the corresponding states of the distribution we have direct access to the output modes. What we want though, is to check the output probability distribution of the photon in the corresponding walk positions. From the initial figure we can define the mapping mode -> walk position. Then, we just have to take care of taking the modes probability distribution and and, for each walk position, sum the probabilities of the corresponding modes." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "# function that takes a state and returns the modes of the photons\n", "def get_mode(state):\n", " modes = [i for i, x in enumerate(state) if x >= 1]\n", " return modes if len(modes) > 1 else modes[0]\n", "# dictionary to map the mode to the position\n", "mode_to_walk_pos_mapping = {\n", " 0: 4,\n", " 1: 2,\n", " 2: 2,\n", " 3: 0,\n", " 4: 0,\n", " 5: -2,\n", " 6: -2,\n", " 7: -4\n", "}" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "|0,0,1,0,0,0,0,0>\n", "|1,0,0,0,0,0,0,0>\n", "|0,1,0,0,0,0,0,0>\n", "|0,0,0,0,0,1,0,0>\n", "|0,0,0,1,0,0,0,0>\n", "|0,0,0,0,1,0,0,0>\n", "|0,0,0,0,0,0,0,1>\n", "|0,0,0,0,0,0,1,0>\n" ] } ], "source": [ "for state in prob_dist[\"results\"].keys():\n", " print(state)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Mode: 0, Probability: 0.0625\n", "Mode: 1, Probability: 0.0625\n", "Mode: 2, Probability: 0.0625\n", "Mode: 3, Probability: 0.0625\n", "Mode: 4, Probability: 0.0625\n", "Mode: 5, Probability: 0.5625000000000001\n", "Mode: 6, Probability: 0.0625\n", "Mode: 7, Probability: 0.0625\n" ] } ], "source": [ "# get output modes from the distribution\n", "modes_probs = [(get_mode(state), prob) for state, prob in prob_dist[\"results\"].items()]\n", "\n", "modes_probs = sorted(modes_probs, key=lambda x: x[0])\n", "\n", "# print modes and probabilities\n", "for mode, prob in modes_probs:\n", " print(\"Mode: {}, Probability: {}\".format(mode, prob))" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Walk position: -4, Probability: 0.0625\n", "Walk position: -3, Probability: 0\n", "Walk position: -2, Probability: 0.6250000000000001\n", "Walk position: -1, Probability: 0\n", "Walk position: 0, Probability: 0.125\n", "Walk position: 1, Probability: 0\n", "Walk position: 2, Probability: 0.125\n", "Walk position: 3, Probability: 0\n", "Walk position: 4, Probability: 0.0625\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAGwCAYAAABVdURTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAq/ElEQVR4nO3dfVSUdcL/8c+AAqKCFApKU5MPqeQDBUnYdme7bLZbqW211HGFpeJeWyxqdttk26RnrFxkM5Ky1J5c6W59aDdvrWZ9OBpFQm6W5vaggRqosYLRvWAz8/uj07T8RIVh4Bq+vl/nzDnOl+818/k6GZ9zXddcl83r9XoFAABgiBCrAwAAAAQS5QYAABiFcgMAAIxCuQEAAEah3AAAAKNQbgAAgFEoNwAAwCi9rA7Q3Twej/bv36/+/fvLZrNZHQcAALSD1+vVkSNHNGTIEIWEnHjfzClXbvbv3y+73W51DAAA4IeamhqdccYZJ5xzypWb/v37S/r2LycqKsriNAAAoD0aGxtlt9t9v8dP5JQrN98dioqKiqLcAADQw7TnlBJOKAYAAEah3AAAAKNQbgAAgFEoNwAAwCiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUSg3AADAKJQbAABgFMoNAAAwCuUGAAAYpZfVAYDu4Jj9mtURTmrP3CusjgAARmDPDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUSg3AADAKJQbAABgFMoNAAAwiuXlpqSkRA6HQxEREUpNTVVFRcUJ5x8+fFi5ubkaPHiwwsPDdc4552jNmjXdlBYAAAQ7S2+/UFZWJqfTqdLSUqWmpqq4uFiTJ0/Wrl27NGjQoGPmt7S06Mc//rEGDRqkV155RQkJCfr88881YMCA7g8PAACCkqXlpqioSDk5OcrOzpYklZaW6rXXXtPixYs1e/bsY+YvXrxY9fX1euutt9S7d29JksPh6M7IAAAgyFl2WKqlpUWVlZVKT0//PkxIiNLT01VeXt7mNq+++qrS0tKUm5uruLg4jRkzRg8//LDcbvdx36e5uVmNjY2tHgAAwFyWlZtDhw7J7XYrLi6u1XhcXJxqa2vb3Oazzz7TK6+8IrfbrTVr1uiee+7RH//4Rz344IPHfZ/CwkJFR0f7Hna7PaDrAAAAwcXyE4o7wuPxaNCgQXr66aeVnJysjIwM3X333SotLT3uNvn5+WpoaPA9ampqujExAADobpadcxMbG6vQ0FDV1dW1Gq+rq1N8fHyb2wwePFi9e/dWaGiob2z06NGqra1VS0uLwsLCjtkmPDxc4eHhgQ0PAACClmV7bsLCwpScnCyXy+Ub83g8crlcSktLa3Obiy66SJ988ok8Ho9v7J///KcGDx7cZrEBAACnHksPSzmdTi1atEjPPfecdu7cqVtuuUVNTU2+b09lZmYqPz/fN/+WW25RfX298vLy9M9//lOvvfaaHn74YeXm5lq1BAAAEGQs/Sp4RkaGDh48qDlz5qi2tlZJSUlau3at7yTj6upqhYR837/sdrvWrVunO+64Q+PGjVNCQoLy8vJ01113WbUEAAAQZGxer9drdYju1NjYqOjoaDU0NCgqKsrqOOgmjtmvWR3hpPbMvcLqCAAQtDry+7tHfVsKAADgZCg3AADAKJQbAABgFMoNAAAwCuUGAAAYhXIDAACMQrkBAABGodwAAACjUG4AAIBRKDcAAMAolBsAAGAUyg0AADAK5QYAABiFcgMAAIxCuQEAAEah3AAAAKNQbgAAgFEoNwAAwCiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUSg3AADAKJQbAABgFMoNAAAwCuUGAAAYhXIDAACMQrkBAABGodwAAACjUG4AAIBRKDcAAMAolBsAAGAUyg0AADAK5QYAABiFcgMAAIxCuQEAAEah3AAAAKNQbgAAgFEoNwAAwCiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjBIU5aakpEQOh0MRERFKTU1VRUXFcecuXbpUNput1SMiIqIb0wIAgGBmebkpKyuT0+lUQUGBqqqqNH78eE2ePFkHDhw47jZRUVH64osvfI/PP/+8GxMDAIBgZnm5KSoqUk5OjrKzs5WYmKjS0lJFRkZq8eLFx93GZrMpPj7e94iLizvu3ObmZjU2NrZ6AAAAc1lablpaWlRZWan09HTfWEhIiNLT01VeXn7c7b766iudddZZstvtmjp1qj788MPjzi0sLFR0dLTvYbfbA7oGAAAQXCwtN4cOHZLb7T5mz0tcXJxqa2vb3GbkyJFavHixVq9erRdffFEej0cTJ07U3r1725yfn5+vhoYG36Ompibg6wAAAMGjl9UBOiotLU1paWm+5xMnTtTo0aP11FNP6YEHHjhmfnh4uMLDw7szIgAAsJCle25iY2MVGhqqurq6VuN1dXWKj49v12v07t1b5513nj755JOuiAgAAHoYS8tNWFiYkpOT5XK5fGMej0cul6vV3pkTcbvd2r59uwYPHtxVMQEAQA9i+WEpp9OprKwspaSkaMKECSouLlZTU5Oys7MlSZmZmUpISFBhYaEk6f7779eFF16o4cOH6/Dhw3rsscf0+eef6+abb7ZyGQAAIEhYXm4yMjJ08OBBzZkzR7W1tUpKStLatWt9JxlXV1crJOT7HUz/+te/lJOTo9raWsXExCg5OVlvvfWWEhMTrVoCAAAIIjav1+u1OkR3amxsVHR0tBoaGhQVFWV1HHQTx+zXrI5wUnvmXmF1BAAIWh35/W35RfwAAAACiXIDAACMQrkBAABGodwAAACjUG4AAIBRKDcAAMAolBsAAGAUyg0AADAK5QYAABiFcgMAAIxCuQEAAEah3AAAAKNQbgAAgFEoNwAAwCiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUSg3AADAKJQbAABgFMoNAAAwCuUGAAAYhXIDAACMQrkBAABGodwAAACjUG4AAIBRKDcAAMAolBsAAGAUyg0AADAK5QYAABiFcgMAAIxCuQEAAEah3AAAAKNQbgAAgFEoNwAAwCiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUYKi3JSUlMjhcCgiIkKpqamqqKho13bLly+XzWbTtGnTujYgAADoMSwvN2VlZXI6nSooKFBVVZXGjx+vyZMn68CBAyfcbs+ePfrtb3+riy++uJuSAgCAnsDyclNUVKScnBxlZ2crMTFRpaWlioyM1OLFi4+7jdvt1vTp03Xfffdp6NChJ3z95uZmNTY2tnoAAABzWVpuWlpaVFlZqfT0dN9YSEiI0tPTVV5eftzt7r//fg0aNEg33XTTSd+jsLBQ0dHRvofdbg9IdgAAEJwsLTeHDh2S2+1WXFxcq/G4uDjV1ta2uc3mzZv17LPPatGiRe16j/z8fDU0NPgeNTU1nc4NAACCVy+rA3TEkSNHNGPGDC1atEixsbHt2iY8PFzh4eFdnAwAAAQLS8tNbGysQkNDVVdX12q8rq5O8fHxx8z/9NNPtWfPHl111VW+MY/HI0nq1auXdu3apWHDhnVtaAAAENQsPSwVFham5ORkuVwu35jH45HL5VJaWtox80eNGqXt27dr27ZtvseUKVN06aWXatu2bZxPAwAArD8s5XQ6lZWVpZSUFE2YMEHFxcVqampSdna2JCkzM1MJCQkqLCxURESExowZ02r7AQMGSNIx4wAA4NRkebnJyMjQwYMHNWfOHNXW1iopKUlr1671nWRcXV2tkBDLv7EOAAB6CJvX6/V2dKOmpib17du3K/J0ucbGRkVHR6uhoUFRUVFWx0E3ccx+zeoIJ7Vn7hVWRwCAoNWR399+7RKJi4vTjTfeqM2bN/sVEAAAoKv4VW5efPFF1dfX64c//KHOOecczZ07V/v37w90NgAAgA7zq9xMmzZNq1at0r59+zRz5kwtW7ZMZ511lq688kqtWLFC33zzTaBzAgAAtEunztQdOHCgnE6n3n//fRUVFenNN9/UtddeqyFDhmjOnDn6+uuvA5UTAACgXTr1bam6ujo999xzWrp0qT7//HNde+21uummm7R371498sgjevvtt/X6668HKisAAMBJ+VVuVqxYoSVLlmjdunVKTEzUr3/9a/3iF7/wXXNGkiZOnKjRo0cHKicAAEC7+FVusrOzdf3112vLli264IIL2pwzZMgQ3X333Z0KBwAA0FF+lZsvvvhCkZGRJ5zTp08fFRQU+BUKAADAX36dUNy/f38dOHDgmPEvv/xSoaGhnQ4FAADgL7/KzfEuatzc3KywsLBOBQIAAOiMDh2WevzxxyVJNptNzzzzjPr16+f7mdvt1qZNmzRq1KjAJgQAAOiADpWb+fPnS/p2z01paWmrQ1BhYWFyOBwqLS0NbEIAAIAO6FC52b17tyTp0ksv1YoVKxQTE9MloQAAAPzl17el1q9fH+gcAAAAAdHucuN0OvXAAw+ob9++cjqdJ5xbVFTU6WAAAAD+aHe5ee+993T06FHfn4/HZrN1PhUAAICf2l1u/vNQFIelAABAsOrUXcEBAACCTbv33PzsZz9r94uuWLHCrzAAAACd1e5yEx0d3ZU5AAAAAqLd5WbJkiVdmQMAACAgOOcGAAAYpd17bs4//3y5XC7FxMTovPPOO+FXvquqqgISDgAAoKPaXW6mTp2q8PBwSdK0adO6Kg8AAECntLvcFBQUtPlnAACAYOLXvaW+s3XrVu3cuVOSlJiYqOTk5ICEAgAA8Jdf5Wbv3r264YYbtGXLFg0YMECSdPjwYU2cOFHLly/XGWecEciMAAAA7ebXt6VuvvlmHT16VDt37lR9fb3q6+u1c+dOeTwe3XzzzYHOCAAA0G5+7bnZuHGj3nrrLY0cOdI3NnLkSC1YsEAXX3xxwMIBAAB0lF97bux2u+8O4f/J7XZryJAhnQ4FAADgL7/KzWOPPaZbb71VW7du9Y1t3bpVeXl5mjdvXsDCAQAAdFS7D0vFxMS0unBfU1OTUlNT1avXty/xzTffqFevXrrxxhu5Dg4AALBMu8tNcXFxF8YAAAAIjHaXm6ysrK7MAQAAEBCduoifJP373/9WS0tLq7GoqKjOviwAAIBf/DqhuKmpSbNmzdKgQYPUt29fxcTEtHoAAABYxa9y87vf/U5///vftXDhQoWHh+uZZ57RfffdpyFDhuj5558PdEYAAIB28+uw1F//+lc9//zzmjRpkrKzs3XxxRdr+PDhOuuss/TSSy9p+vTpgc4JAADQLn7tuamvr9fQoUMlfXt+TX19vSTpBz/4gTZt2hS4dAAAAB3kV7kZOnSodu/eLUkaNWqUXn75ZUnf7tH57kaaAAAAVvCr3GRnZ+sf//iHJGn27NkqKSlRRESE7rjjDt15550BDQgAANARfp1zc8cdd/j+nJ6erp07d6qqqkrDhw/XuHHjAhYOAACgozp9nRtJcjgccjgcgXgpAACATvHrsJQkuVwuXXnllRo2bJiGDRumK6+8Um+++WYgswEAAHSYX+XmySef1OWXX67+/fsrLy9PeXl5ioqK0k9/+lOVlJQEOiMAAEC7+VVuHn74Yc2fP19//vOfddttt+m2227TsmXLNH/+fD388MMdfr2SkhI5HA5FREQoNTVVFRUVx527YsUKpaSkaMCAAerbt6+SkpL0wgsv+LMMAABgIL/KzeHDh3X55ZcfM37ZZZepoaGhQ69VVlYmp9OpgoICVVVVafz48Zo8ebIOHDjQ5vzTTjtNd999t8rLy/X+++8rOztb2dnZWrdunT9LAQAAhvGr3EyZMkUrV648Znz16tW68sorO/RaRUVFysnJUXZ2thITE1VaWqrIyEgtXry4zfmTJk3S1VdfrdGjR2vYsGHKy8vTuHHjtHnzZn+WAgAADNPub0s9/vjjvj8nJibqoYce0oYNG5SWliZJevvtt7Vlyxb95je/afebt7S0qLKyUvn5+b6xkJAQpaenq7y8/KTbe71e/f3vf9euXbv0yCOPtDmnublZzc3NvueNjY3tzgcAAHqedpeb+fPnt3oeExOjHTt2aMeOHb6xAQMGaPHixfrDH/7Qrtc8dOiQ3G634uLiWo3HxcXpo48+Ou52DQ0NSkhIUHNzs0JDQ/Xkk0/qxz/+cZtzCwsLdd9997UrDwAA6PnaXW6+u91CMOjfv7+2bdumr776Si6XS06nU0OHDtWkSZOOmZufny+n0+l73tjYKLvd3o1pAQBAd+r0Rfy8Xq8kyWazdXjb2NhYhYaGqq6urtV4XV2d4uPjj7tdSEiIhg8fLklKSkrSzp07VVhY2Ga5CQ8PV3h4eIezAQCAnsnvi/g9//zzGjt2rPr06aM+ffpo3LhxHf5KdlhYmJKTk+VyuXxjHo9HLpfLdy5Pe3g8nlbn1QAAgFOXX3tuioqKdM8992jWrFm66KKLJEmbN2/WzJkzdejQoVb3njoZp9OprKwspaSkaMKECSouLlZTU5Oys7MlSZmZmUpISFBhYaGkb8+hSUlJ0bBhw9Tc3Kw1a9bohRde0MKFC/1ZCgAAMIxf5WbBggVauHChMjMzfWNTpkzRueeeq3vvvbdD5SYjI0MHDx7UnDlzVFtbq6SkJK1du9Z3knF1dbVCQr7fwdTU1KRf//rX2rt3r/r06aNRo0bpxRdfVEZGhj9LAQAAhrF5vztppgMiIiL0wQcf+M57+c7HH3+ssWPH6t///nfAAgZaY2OjoqOj1dDQoKioKKvjoJs4Zr9mdYST2jP3CqsjAEDQ6sjvb7/OuRk+fLhefvnlY8bLyso0YsQIf14SAAAgIPw6LHXfffcpIyNDmzZt8p1zs2XLFrlcrjZLDwAAQHfxa8/NNddco4qKCsXGxmrVqlVatWqVYmNjVVFRoauvvjrQGQEAANqtw3tujh49ql/96le655579OKLL3ZFJgAAAL91eM9N79699Ze//KUrsgAAAHSaX4elpk2bplWrVgU4CgAAQOf5dULxiBEjdP/992vLli1KTk5W3759W/38tttuC0g4AACAjvKr3Dz77LMaMGCAKisrVVlZ2epnNpuNcgMAACzjV7n5zzuEd+bGmQAAAIHm940zn332WY0ZM0YRERGKiIjQmDFj9MwzzwQyGwAAQIf5tedmzpw5Kioq0q233uq7e3d5ebnuuOMOVVdX6/777w9oSAAAgPbyq9wsXLhQixYt0g033OAbmzJlisaNG6dbb72VcgMAACzj12Gpo0ePKiUl5Zjx5ORkffPNN50OBQAA4C+/ys2MGTO0cOHCY8affvppTZ8+vdOhAAAA/OXXYSnp2xOKX3/9dV144YWSpHfeeUfV1dXKzMyU0+n0zSsqKup8SgAAgHbyq9x88MEHOv/88yVJn376qSQpNjZWsbGx+uCDD3zz+Ho4AADobn6Vm/Xr1wc6BwAAQED4fZ0bAACAYES5AQAARqHcAAAAo1BuAACAUSg3AADAKJQbAABgFMoNAAAwCuUGAAAYhXIDAACMQrkBAABGodwAAACjUG4AAIBRKDcAAMAolBsAAGAUyg0AADAK5QYAABiFcgMAAIxCuQEAAEah3AAAAKNQbgAAgFEoNwAAwCiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUYKi3JSUlMjhcCgiIkKpqamqqKg47txFixbp4osvVkxMjGJiYpSenn7C+QAA4NRiebkpKyuT0+lUQUGBqqqqNH78eE2ePFkHDhxoc/6GDRt0ww03aP369SovL5fdbtdll12mffv2dXNyAAAQjGxer9drZYDU1FRdcMEFeuKJJyRJHo9Hdrtdt956q2bPnn3S7d1ut2JiYvTEE08oMzPzpPMbGxsVHR2thoYGRUVFdTo/egbH7NesjnBSe+ZeYXUEAAhaHfn9bemem5aWFlVWVio9Pd03FhISovT0dJWXl7frNb7++msdPXpUp512Wps/b25uVmNjY6sHAAAwl6Xl5tChQ3K73YqLi2s1HhcXp9ra2na9xl133aUhQ4a0Kkj/qbCwUNHR0b6H3W7vdG4AABC8LD/npjPmzp2r5cuXa+XKlYqIiGhzTn5+vhoaGnyPmpqabk4JAAC6Uy8r3zw2NlahoaGqq6trNV5XV6f4+PgTbjtv3jzNnTtXb775psaNG3fceeHh4QoPDw9IXgAAEPws3XMTFham5ORkuVwu35jH45HL5VJaWtpxt3v00Uf1wAMPaO3atUpJSemOqAAAoIewdM+NJDmdTmVlZSklJUUTJkxQcXGxmpqalJ2dLUnKzMxUQkKCCgsLJUmPPPKI5syZo2XLlsnhcPjOzenXr5/69etn2ToAAEBwsLzcZGRk6ODBg5ozZ45qa2uVlJSktWvX+k4yrq6uVkjI9zuYFi5cqJaWFl177bWtXqegoED33ntvd0YHAABByPLr3HQ3rnNzauI6NwDQs/WY69wAAAAEGuUGAAAYhXIDAACMQrkBAABGodwAAACjUG4AAIBRKDcAAMAolBsAAGAUyg0AADAK5QYAABiFcgMAAIxCuQEAAEah3AAAAKNQbgAAgFEoNwAAwCiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUSg3AADAKJQbAABgFMoNAAAwCuUGAAAYhXIDAACMQrkBAABGodwAAACjUG4AAIBRKDcAAMAolBsAAGAUyg0AADAK5QYAABiFcgMAAIzSy+oAAE5tjtmvWR3hpPbMvcLqCN2OzwU9GXtuAACAUSg3AADAKJQbAABgFMoNAAAwCuUGAAAYhXIDAACMQrkBAABGodwAAACjUG4AAIBRKDcAAMAolpebkpISORwORUREKDU1VRUVFced++GHH+qaa66Rw+GQzWZTcXFx9wUFAAA9gqXlpqysTE6nUwUFBaqqqtL48eM1efJkHThwoM35X3/9tYYOHaq5c+cqPj6+m9MCAICewNJyU1RUpJycHGVnZysxMVGlpaWKjIzU4sWL25x/wQUX6LHHHtP111+v8PDwdr1Hc3OzGhsbWz0AAIC5LCs3LS0tqqysVHp6+vdhQkKUnp6u8vLygL1PYWGhoqOjfQ+73R6w1wYAAMHHsnJz6NAhud1uxcXFtRqPi4tTbW1twN4nPz9fDQ0NvkdNTU3AXhsAAASfXlYH6Grh4eHtPoQFAAB6Psv23MTGxio0NFR1dXWtxuvq6jhZGAAA+M2ychMWFqbk5GS5XC7fmMfjkcvlUlpamlWxAABAD2fpYSmn06msrCylpKRowoQJKi4uVlNTk7KzsyVJmZmZSkhIUGFhoaRvT0LesWOH78/79u3Ttm3b1K9fPw0fPtyydQAAgOBhabnJyMjQwYMHNWfOHNXW1iopKUlr1671nWRcXV2tkJDvdy7t379f5513nu/5vHnzNG/ePF1yySXasGFDd8cHAABByPITimfNmqVZs2a1+bP/v7A4HA55vd5uSAUAAHoqy2+/AAAAEEiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUSg3AADAKJQbAABgFMoNAAAwCuUGAAAYhXIDAACMQrkBAABG6WV1ANM4Zr9mdYST2jP3CqsjAAD8xO+Zk2PPDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUSg3AADAKJQbAABgFMoNAAAwCuUGAAAYhXIDAACMQrkBAABGodwAAACjUG4AAIBRKDcAAMAolBsAAGAUyg0AADAK5QYAABiFcgMAAIxCuQEAAEah3AAAAKNQbgAAgFEoNwAAwCiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAAAAo1BuAACAUYKi3JSUlMjhcCgiIkKpqamqqKg44fz/+Z//0ahRoxQREaGxY8dqzZo13ZQUAAAEO8vLTVlZmZxOpwoKClRVVaXx48dr8uTJOnDgQJvz33rrLd1www266aab9N5772natGmaNm2aPvjgg25ODgAAgpHl5aaoqEg5OTnKzs5WYmKiSktLFRkZqcWLF7c5/09/+pMuv/xy3XnnnRo9erQeeOABnX/++XriiSe6OTkAAAhGvax885aWFlVWVio/P983FhISovT0dJWXl7e5TXl5uZxOZ6uxyZMna9WqVW3Ob25uVnNzs+95Q0ODJKmxsbGT6dvmaf66S143kLpq7cGMzyV48dkEJz6X4HWqfjbfvabX6z3pXEvLzaFDh+R2uxUXF9dqPC4uTh999FGb29TW1rY5v7a2ts35hYWFuu+++44Zt9vtfqbu+aKLrU6AtvC5BC8+m+DE5xK8uvKzOXLkiKKjo084x9Jy0x3y8/Nb7enxeDyqr6/X6aefLpvNZmGyk2tsbJTdbldNTY2ioqKsjtNpJq2HtQQnk9YimbUe1hKcetJavF6vjhw5oiFDhpx0rqXlJjY2VqGhoaqrq2s1XldXp/j4+Da3iY+P79D88PBwhYeHtxobMGCA/6EtEBUVFfT/0XWESethLcHJpLVIZq2HtQSnnrKWk+2x+Y6lJxSHhYUpOTlZLpfLN+bxeORyuZSWltbmNmlpaa3mS9Ibb7xx3PkAAODUYvlhKafTqaysLKWkpGjChAkqLi5WU1OTsrOzJUmZmZlKSEhQYWGhJCkvL0+XXHKJ/vjHP+qKK67Q8uXLtXXrVj399NNWLgMAAAQJy8tNRkaGDh48qDlz5qi2tlZJSUlau3at76Th6upqhYR8v4Np4sSJWrZsmf7whz/o97//vUaMGKFVq1ZpzJgxVi2hy4SHh6ugoOCYw2o9lUnrYS3ByaS1SGath7UEJ5PW8p9s3vZ8pwoAAKCHsPwifgAAAIFEuQEAAEah3AAAAKNQbgAAgFEoNz1Qc3OzkpKSZLPZtG3bNqvj+GXKlCk688wzFRERocGDB2vGjBnav3+/1bE6bM+ePbrpppt09tlnq0+fPho2bJgKCgrU0tJidTS/PPTQQ5o4caIiIyN73MUuJamkpEQOh0MRERFKTU1VRUWF1ZH8smnTJl111VUaMmSIbDbbce+dF+wKCwt1wQUXqH///ho0aJCmTZumXbt2WR3LbwsXLtS4ceN8F7xLS0vT//7v/1odKyDmzp0rm82m22+/3eooAUG56YF+97vftevy08Hs0ksv1csvv6xdu3bpL3/5iz799FNde+21VsfqsI8++kgej0dPPfWUPvzwQ82fP1+lpaX6/e9/b3U0v7S0tOi6667TLbfcYnWUDisrK5PT6VRBQYGqqqo0fvx4TZ48WQcOHLA6Woc1NTVp/PjxKikpsTpKp2zcuFG5ubl6++239cYbb+jo0aO67LLL1NTUZHU0v5xxxhmaO3euKisrtXXrVv3whz/U1KlT9eGHH1odrVPeffddPfXUUxo3bpzVUQLHix5lzZo13lGjRnk//PBDryTve++9Z3WkgFi9erXXZrN5W1parI7SaY8++qj37LPPtjpGpyxZssQbHR1tdYwOmTBhgjc3N9f33O12e4cMGeItLCy0MFXnSfKuXLnS6hgBceDAAa8k78aNG62OEjAxMTHeZ555xuoYfjty5Ih3xIgR3jfeeMN7ySWXePPy8qyOFBDsuelB6urqlJOToxdeeEGRkZFWxwmY+vp6vfTSS5o4caJ69+5tdZxOa2ho0GmnnWZ1jFNKS0uLKisrlZ6e7hsLCQlRenq6ysvLLUyG/9TQ0CBJRvz7cLvdWr58uZqamnr07X9yc3N1xRVXtPq3YwLKTQ/h9Xr1y1/+UjNnzlRKSorVcQLirrvuUt++fXX66aerurpaq1evtjpSp33yySdasGCBfvWrX1kd5ZRy6NAhud1u35XNvxMXF6fa2lqLUuE/eTwe3X777brooot69BXlt2/frn79+ik8PFwzZ87UypUrlZiYaHUsvyxfvlxVVVW+2xuZhHJjsdmzZ8tms53w8dFHH2nBggU6cuSI8vPzrY58XO1dy3fuvPNOvffee3r99dcVGhqqzMxMeYPkgtkdXYsk7du3T5dffrmuu+465eTkWJT8WP6sBQi03NxcffDBB1q+fLnVUTpl5MiR2rZtm9555x3dcsstysrK0o4dO6yO1WE1NTXKy8vTSy+9pIiICKvjBBy3X7DYwYMH9eWXX55wztChQ/Xzn/9cf/3rX2Wz2XzjbrdboaGhmj59up577rmujnpS7V1LWFjYMeN79+6V3W7XW2+9FRS7eDu6lv3792vSpEm68MILtXTp0lb3Q7OaP5/L0qVLdfvtt+vw4cNdnC4wWlpaFBkZqVdeeUXTpk3zjWdlZenw4cM9eq+gzWbTypUrW62rp5k1a5ZWr16tTZs26eyzz7Y6TkClp6dr2LBheuqpp6yO0iGrVq3S1VdfrdDQUN+Y2+2WzWZTSEiImpubW/2sp7H8xpmnuoEDB2rgwIEnnff444/rwQcf9D3fv3+/Jk+erLKyMqWmpnZlxHZr71ra4vF4JH37Nfdg0JG17Nu3T5deeqmSk5O1ZMmSoCo2Uuc+l54iLCxMycnJcrlcvhLg8Xjkcrk0a9Ysa8Odwrxer2699VatXLlSGzZsMK7YSN/+dxYs/9/qiB/96Efavn17q7Hs7GyNGjVKd911V48uNhLlpsc488wzWz3v16+fJGnYsGE644wzrIjkt3feeUfvvvuufvCDHygmJkaffvqp7rnnHg0bNiwo9tp0xL59+zRp0iSdddZZmjdvng4ePOj7WXx8vIXJ/FNdXa36+npVV1fL7Xb7rqM0fPhw339zwcrpdCorK0spKSmaMGGCiouL1dTUpOzsbKujddhXX32lTz75xPd89+7d2rZtm0477bRj/l8QzHJzc7Vs2TKtXr1a/fv3953/FB0drT59+licruPy8/P1k5/8RGeeeaaOHDmiZcuWacOGDVq3bp3V0Tqsf//+x5z79N05kD35nCgfS7+rBb/t3r27x34V/P333/deeuml3tNOO80bHh7udTgc3pkzZ3r37t1rdbQOW7JkiVdSm4+eKCsrq821rF+/3upo7bJgwQLvmWee6Q0LC/NOmDDB+/bbb1sdyS/r169v83PIysqyOlqHHO/fxpIlS6yO5pcbb7zRe9ZZZ3nDwsK8AwcO9P7oRz/yvv7661bHChiTvgrOOTcAAMAowXVyAAAAQCdRbgAAgFEoNwAAwCiUGwAAYBTKDQAAMArlBgAAGIVyAwAAjEK5AQAARqHcAOjxNmzYIJvNdtIbfTocDhUXF3dLJgDW4QrFAHq8lpYW1dfXKy4uTjab7bh3NT948KD69u2ryMhIa4IC6BbcOBNAjxcWFtauG5Wafnd0AN/isBSAbjFp0iTNmjVLs2bNUnR0tGJjY3XPPffou53H//rXv5SZmamYmBhFRkbqJz/5iT7++GPf9p9//rmuuuoqxcTEqG/fvjr33HO1Zs0aSa0PS23YsEHZ2dlqaGiQzWaTzWbTvffeK+nYw1LV1dWaOnWq+vXrp6ioKP385z9XXV2d7+f33nuvkpKS9MILL8jhcCg6OlrXX3+9jhw50vV/YQD8RrkB0G2ee+459erVSxUVFfrTn/6koqIiPfPMM5KkX/7yl9q6dateffVVlZeXy+v16qc//amOHj0qScrNzVVzc7M2bdqk7du365FHHlG/fv2OeY+JEyequLhYUVFR+uKLL/TFF1/ot7/97THzPB6Ppk6dqvr6em3cuFFvvPGGPvvsM2VkZLSa9+mnn2rVqlX629/+pr/97W/auHGj5s6d2wV/OwAChcNSALqN3W7X/PnzZbPZNHLkSG3fvl3z58/XpEmT9Oqrr2rLli2aOHGiJOmll16S3W7XqlWrdN1116m6ulrXXHONxo4dK0kaOnRom+8RFham6Oho2Wy2Ex6qcrlc2r59u3bv3i273S5Jev7553Xuuefq3Xff1QUXXCDp2xK0dOlS9e/fX5I0Y8YMuVwuPfTQQwH7ewEQWOy5AdBtLrzwQtlsNt/ztLQ0ffzxx9qxY4d69eql1NRU389OP/10jRw5Ujt37pQk3XbbbXrwwQd10UUXqaCgQO+//36nsuzcuVN2u91XbCQpMTFRAwYM8L2n9O2hrO+KjSQNHjxYBw4c6NR7A+halBsAPcLNN9+szz77TDNmzND27duVkpKiBQsWdPn79u7du9Vzm80mj8fT5e8LwH+UGwDd5p133mn1/O2339aIESOUmJiob775ptXPv/zyS+3atUuJiYm+MbvdrpkzZ2rFihX6zW9+o0WLFrX5PmFhYXK73SfMMnr0aNXU1KimpsY3tmPHDh0+fLjVewLoeSg3ALpNdXW1nE6ndu3apT//+c9asGCB8vLyNGLECE2dOlU5OTnavHmz/vGPf+gXv/iFEhISNHXqVEnS7bffrnXr1mn37t2qqqrS+vXrNXr06Dbfx+Fw6KuvvpLL5dKhQ4f09ddfHzMnPT1dY8eO1fTp01VVVaWKigplZmbqkksuUUpKSpf+PQDoWpQbAN0mMzNT//d//6cJEyYoNzdXeXl5+u///m9J0pIlS5ScnKwrr7xSaWlp8nq9WrNmje+wkNvtVm5urkaPHq3LL79c55xzjp588sk232fixImaOXOmMjIyNHDgQD366KPHzLHZbFq9erViYmL0X//1X0pPT9fQoUNVVlbWdX8BALoFVygG0C0mTZqkpKQkbn8AoMux5wYAABiFcgMAAIzCYSkAAGAU9twAAACjUG4AAIBRKDcAAMAolBsAAGAUyg0AADAK5QYAABiFcgMAAIxCuQEAAEb5fwsYjWsammtPAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# get the walk positions distribution\n", "walk_pos = range(-steps, steps+1)\n", "walk_probs = [0]*(2*steps+1)\n", "\n", "\n", "for i, w_p in enumerate(walk_pos):\n", " idxs = [index for (index, (mode, prob)) in enumerate(modes_probs) if mode_to_walk_pos_mapping[mode] == w_p]\n", " if len(idxs) > 0:\n", " walk_probs[i] = sum([modes_probs[idx][1] for idx in idxs])\n", " else:\n", " walk_probs[i] = 0\n", "\n", "# print walk positions and probabilities\n", "for w_p, w_p_p in zip(walk_pos, walk_probs):\n", " print(\"Walk position: {}, Probability: {}\".format(w_p, w_p_p))\n", "\n", "# plot the walk positions distribution\n", "plt.bar(walk_pos, walk_probs)\n", "plt.xticks(walk_pos)\n", "plt.xlabel(\"position\")\n", "plt.ylabel(\"probability\")\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Two photons quantum walk\n", "Now we can follow the same procedure as before, but with two input photons in the two input modes (3 and 4)." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "output distribution: {\n", "\t|1,0,1,0,0,0,0,0>: 0.0625\n", "\t|0,0,0,1,0,1,0,0>: 0.0625\n", "\t|2,0,0,0,0,0,0,0>: 0.0078125\n", "\t|0,0,0,1,0,0,1,0>: 0.015625\n", "\t|1,0,0,0,1,0,0,0>: 0.015625\n", "\t|0,0,0,0,0,1,1,0>: 0.0625\n", "\t|1,1,0,0,0,0,0,0>: 0.015625\n", "\t|1,0,0,0,0,1,0,0>: 0.015625000000000007\n", "\t|0,1,0,0,1,0,0,0>: 0.015625\n", "\t|0,0,1,1,0,0,0,0>: 0.015625000000000007\n", "\t|0,2,0,0,0,0,0,0>: 0.0078125\n", "\t|0,0,0,0,0,0,1,1>: 0.015625\n", "\t|0,1,1,0,0,0,0,0>: 0.0625\n", "\t|0,1,0,0,0,1,0,0>: 0.015625000000000007\n", "\t|0,0,2,0,0,0,0,0>: 0.07031250000000001\n", "\t|0,0,1,0,1,0,0,0>: 0.0625\n", "\t|0,0,0,0,0,1,0,1>: 0.0625\n", "\t|0,0,1,0,0,1,0,0>: 0.2500000000000001\n", "\t|0,0,1,0,0,0,1,0>: 0.015625000000000007\n", "\t|0,0,1,0,0,0,0,1>: 0.015625000000000007\n", "\t|0,0,0,2,0,0,0,0>: 0.0078125\n", "\t|0,0,0,1,0,0,0,1>: 0.015625\n", "\t|0,0,0,0,2,0,0,0>: 0.0078125\n", "\t|0,0,0,0,1,1,0,0>: 0.015625000000000007\n", "\t|0,0,0,0,0,2,0,0>: 0.07031250000000001\n", "\t|0,0,0,0,0,0,2,0>: 0.0078125\n", "\t|0,0,0,0,0,0,0,2>: 0.0078125\n", "}\n" ] } ], "source": [ "# two photons input state\n", "in_list = [0]*n\n", "in_list[3], in_list[4] = 1, 1\n", "in_state = pcvl.BasicState(in_list)\n", "\n", "# select a backend and define the simulator on the circuit\n", "simulator = Simulator(SLOSBackend())\n", "simulator.set_circuit(circuit)\n", "\n", "# define a source and input distribution due to source noise\n", "source = Source(losses=0, indistinguishability=1)\n", "input_distribution = source.generate_distribution(expected_input=in_state)\n", "\n", "prob_dist = simulator.probs_svd(input_distribution)\n", "print(\"output distribution:\", prob_dist[\"results\"])" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# get output modes from the distribution\n", "modes = [get_mode(state) for state in prob_dist[\"results\"].keys()]\n", "## take care of the case where there is only one mode\n", "modes = [m if isinstance(m, list) else [m,m] for m in modes]\n", "\n", "# get the probabilities of the modes\n", "probs = np.array([[0]*n]*n, dtype=np.float64)\n", "\n", "for m, prob in zip(modes, prob_dist[\"results\"].values()):\n", " probs[m[0], m[1]] = prob" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "# get the walk positions distribution\n", "walk_pos = range(-steps, steps+1)\n", "\n", "walk_probs = np.array([[0]*(2*steps+1)]*(2*steps+1), dtype=np.float64)\n", "for i in range(n):\n", " for j in range(n):\n", " w_i = mode_to_walk_pos_mapping[i]+steps\n", " w_j = mode_to_walk_pos_mapping[j]+steps\n", " walk_probs[w_i, w_j] += probs[i,j]" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# plot as 3dbar\n", "x, y = np.meshgrid(walk_pos, walk_pos)\n", "cmap = plt.get_cmap('jet') # Get desired colormap\n", "max_height = np.max(walk_probs.flatten()) \n", "min_height = np.min(walk_probs.flatten())\n", "# scale each z to [0,1], and get their rgb values\n", "rgba = [cmap((k-min_height)/max_height) if k!=0 else (0,0,0,0) for k in walk_probs.flatten()] \n", "fig = plt.figure(figsize=(6, 8))\n", "ax = fig.add_subplot(111, projection='3d')\n", "ax.bar3d(x.flatten(), y.flatten(), np.zeros((2*steps+1)*(2*steps+1)), 1, 1, walk_probs.flatten(), color=rgba)\n", "ax.set_xlabel(\"position\")\n", "ax.set_ylabel(\"position\")\n", "ax.set_zlabel(\"probability\")\n", "ax.set_box_aspect(aspect=None, zoom=0.8)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Bosonic-fermionic quantum walks\n", "Moreover, we can select an entangled state as the input state and observe that the output distribution behaves differently with respect to the state statistic." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "bosonic output distribution: {\n", "\t|1,0,0,0,1,0,0,0>: 0.015624999999999997\n", "\t|0,0,0,1,0,0,1,0>: 0.015624999999999997\n", "\t|0,0,0,0,0,1,1,0>: 0.06250000000000001\n", "\t|1,1,0,0,0,0,0,0>: 0.015624999999999997\n", "\t|0,0,1,0,0,0,0,1>: 0.015625000000000003\n", "\t|0,0,1,0,0,1,0,0>: 0.25000000000000006\n", "\t|0,0,0,0,0,1,0,1>: 0.06250000000000001\n", "\t|0,1,1,0,0,0,0,0>: 0.06250000000000001\n", "\t|0,0,0,0,0,0,1,1>: 0.015624999999999997\n", "\t|0,0,0,1,0,1,0,0>: 0.06250000000000001\n", "\t|2,0,0,0,0,0,0,0>: 0.007812499999999998\n", "\t|0,0,0,0,0,0,2,0>: 0.007812499999999998\n", "\t|0,0,0,1,0,0,0,1>: 0.015624999999999997\n", "\t|0,0,0,0,0,0,0,2>: 0.007812499999999998\n", "\t|0,1,0,0,1,0,0,0>: 0.015624999999999997\n", "\t|1,0,0,0,0,1,0,0>: 0.015625000000000003\n", "\t|0,0,1,0,1,0,0,0>: 0.06250000000000001\n", "\t|1,0,1,0,0,0,0,0>: 0.06250000000000001\n", "\t|0,0,0,0,2,0,0,0>: 0.007812499999999998\n", "\t|0,0,1,1,0,0,0,0>: 0.015625000000000003\n", "\t|0,2,0,0,0,0,0,0>: 0.007812499999999998\n", "\t|0,0,0,0,1,1,0,0>: 0.015625000000000003\n", "\t|0,0,2,0,0,0,0,0>: 0.07031250000000001\n", "\t|0,0,1,0,0,0,1,0>: 0.015625000000000003\n", "\t|0,1,0,0,0,1,0,0>: 0.015625000000000003\n", "\t|0,0,0,0,0,2,0,0>: 0.07031250000000001\n", "\t|0,0,0,2,0,0,0,0>: 0.007812499999999998\n", "}\n", "fermionic output distribution: {\n", "\t|0,0,0,0,0,1,1,0>: 0.015625000000000003\n", "\t|1,0,0,0,0,0,0,1>: 0.015624999999999997\n", "\t|0,0,1,0,0,0,0,1>: 0.06250000000000001\n", "\t|0,1,0,0,0,0,1,0>: 0.015624999999999997\n", "\t|0,0,0,0,1,0,0,1>: 0.015624999999999997\n", "\t|0,0,1,0,0,1,0,0>: 0.39062500000000006\n", "\t|0,0,0,0,0,1,0,1>: 0.015625000000000003\n", "\t|0,1,0,0,0,0,0,1>: 0.015624999999999997\n", "\t|1,0,0,0,0,1,0,0>: 0.06250000000000001\n", "\t|0,0,1,0,1,0,0,0>: 0.015625000000000003\n", "\t|1,0,1,0,0,0,0,0>: 0.015625000000000003\n", "\t|0,0,0,0,1,0,1,0>: 0.015624999999999997\n", "\t|0,0,0,0,1,1,0,0>: 0.06250000000000001\n", "\t|1,0,0,0,0,0,1,0>: 0.015624999999999997\n", "\t|0,0,0,1,1,0,0,0>: 0.015624999999999997\n", "\t|0,0,1,0,0,0,1,0>: 0.06250000000000001\n", "\t|0,1,1,0,0,0,0,0>: 0.015625000000000003\n", "\t|0,0,1,1,0,0,0,0>: 0.06250000000000001\n", "\t|0,1,0,0,0,1,0,0>: 0.06250000000000001\n", "\t|0,0,0,1,0,1,0,0>: 0.015625000000000003\n", "\t|1,0,0,1,0,0,0,0>: 0.015624999999999997\n", "\t|0,1,0,1,0,0,0,0>: 0.015624999999999997\n", "}\n" ] } ], "source": [ "# two entangled input states\n", "bosonic_state = pcvl.StateVector(\"|0,0,0,{1},{2},0,0,0>\") + pcvl.StateVector(\"|0,0,0,{2},{1},0,0,0>\")\n", "fermionic_state = pcvl.StateVector(\"|0,0,0,{1},{2},0,0,0>\") - pcvl.StateVector(\"|0,0,0,{2},{1},0,0,0>\")\n", "\n", "\n", "# select a backend and define the simulator on the circuit\n", "simulator = Simulator(SLOSBackend())\n", "simulator.set_circuit(circuit)\n", "\n", "bosonic_prob_dist = simulator.probs(bosonic_state)\n", "fermionic_prob_dist = simulator.probs(fermionic_state)\n", "\n", "print(\"bosonic output distribution:\", bosonic_prob_dist)\n", "print(\"fermionic output distribution:\", fermionic_prob_dist)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "# get output modes from the distributions\n", "bosonic_modes = [get_mode(state) for state, _ in bosonic_prob_dist.items()]\n", "bosonic_modes = [m if isinstance(m, list) else [m,m] for m in bosonic_modes]\n", "fermionic_modes = [get_mode(state) for state, _ in fermionic_prob_dist.items()]\n", "fermionic_modes = [m if isinstance(m, list) else [m,m] for m in fermionic_modes]\n", "\n", "# get the probabilities of the modes\n", "bosonic_probs = np.array([[0]*n]*n, dtype=np.float64)\n", "for m, (_, prob) in zip(bosonic_modes, bosonic_prob_dist.items()):\n", " bosonic_probs[m[0], m[1]] = prob\n", "\n", "fermionic_probs = np.array([[0]*n]*n, dtype=np.float64)\n", "for m, (_, prob) in zip(fermionic_modes, fermionic_prob_dist.items()):\n", " fermionic_probs[m[0], m[1]] = prob\n", "\n", "# get the walk positions distributions\n", "walk_pos = range(-steps, steps+1)\n", "\n", "bosonic_walk_probs = np.array([[0]*(2*steps+1)]*(2*steps+1), dtype=np.float64)\n", "fermionic_walk_probs = np.array([[0]*(2*steps+1)]*(2*steps+1), dtype=np.float64)\n", "for i in range(n):\n", " for j in range(n):\n", " w_i = mode_to_walk_pos_mapping[i]+steps\n", " w_j = mode_to_walk_pos_mapping[j]+steps\n", " bosonic_walk_probs[w_i, w_j] += bosonic_probs[i,j]\n", " fermionic_walk_probs[w_i, w_j] += fermionic_probs[i,j]" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxoAAAGKCAYAAACLuTc4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOy9eXxcBbn//zmzZ53sSZM0a9O06d50jSjoRQqIgoIKLigI+ruIXi6ogCiLyo6AYgVFEbiA8HX3XhDupVAotgXbZrLv+77Mkm32M+f3RziHM5NZzpk5k0mb5/169QVNZs45M515nvNsn4fhOI4DQRAEQRAEQRCEgqgSfQEEQRAEQRAEQZx5UKBBEARBEARBEITiUKBBEARBEARBEITiUKBBEARBEARBEITiUKBBEARBEARBEITiUKBBEARBEARBEITiUKBBEARBEARBEITiUKBBEARBEARBEITiUKBBEARBEARBEITiUKBBLCt33nknGIbB9PR0oi8lap5++mkwDIP+/v5EXwpBEAQhkX/961+oq6tDSkoKGIaByWRK9CUtiz/p7+8HwzB4+umn43YOggiFJtEXQBAEQRAEEU88Hg8++9nPwmAw4JFHHkFycjJKS0sTfVkEccbDcBzHJfoiiNXDnXfeibvuugtTU1PIyclJ9OVEBcuy8Hg80Ov1YBgm0ZdDEARBRKC9vR0bN27Ek08+iWuuuSbRlyOwHP6E4zi4XC5otVqo1eq4nIMgQkEVDYKQiVqtJmNNEARxGjE5OQkAyMjIUOR4CwsLSElJifk4y+FPGIaBwWCI6zkIIhQ0o0EkhOnpaXzuc59Deno6srOz8R//8R9wOp3C771eL3784x+jsrISer0eZWVl+P73vw+Xy+V3nBMnTuDAgQPIyclBUlISysvLcfXVV/s9ZmFhATfddBPWrl0LvV6P6upqPPTQQwgs5jEMg+uvvx5//etfsXnzZuj1emzatAmvvvqq3+NC9dT+4x//wNlnn420tDSkp6dj9+7deOGFFxR4twiCIIho+epXv4qzzz4bAPDZz34WDMPgnHPOAbBY6bjsssuQlZUFg8GAXbt24e9//7vf83mb/9Zbb+G6665DXl4eiouLAQDnnHMONm/ejMbGRpx99tlITk7GunXr8Mc//hEA8NZbb2Hv3r1ISkpCdXU1Xn/99aDHDvQnv/zlL7Fp0ybo9XoUFhbim9/8Jmw2m99j+HO3trbiox/9KJKTk1FUVIQHHnjA73GhZjTa29vxuc99Drm5ucL13XbbbXLfXoIICwUaREL43Oc+B6fTiXvvvRcXXnghfv7zn+PrX/+68PtrrrkGt99+O3bu3IlHHnkEZ599Nu69915cfvnlwmMmJydx3nnnob+/H7fccgsee+wxfPGLX8Tx48eFx3Ach0996lN45JFHcP755+Phhx9GdXU1vvvd7+LGG29ccl3vvPMOrrvuOlx++eV44IEH4HQ6cemll8JsNod9PU8//TQ+8YlPwGKx4NZbb8V9992H7du3LwlSCIIgiOXlG9/4Br7//e8DAL797W/jv/7rv3DbbbehpaUF+/btQ1tbG2655Rb89Kc/RUpKCi655BL85S9/WXKc6667Dq2trbj99ttxyy23CD+3Wq246KKLsHfvXjzwwAPQ6/W4/PLL8dJLL+Hyyy/HhRdeiPvuuw8LCwu47LLLMDc3F/Z677zzTnzzm99EYWEhfvrTn+LSSy/Fr371K5x33nnweDx+j7VarTj//POxbds2/PSnP8WGDRtw88034x//+EfYczQ2NmLv3r144403cO211+JnP/sZLrnkEvz3f/+31LeVIKTBEcQycscdd3AAuE996lN+P7/uuus4AFxDQwNnMpk4ANw111zj95jvfOc7HADujTfe4DiO4/7yl79wALh//etfIc/317/+lQPA/eQnP/H7+WWXXcYxDMN1d3cLPwPA6XQ6v581NDRwALjHHntM+Nnvfvc7DgDX19fHcRzH2Ww2Li0tjdu7dy/ncDj8zuPz+SS8KwRBEEQ8efPNNzkA3B/+8AfhZ//2b//GbdmyhXM6ncLPfD4fV1dXx1VVVQk/423+WWedxXm9Xr/jnn322RwA7oUXXhB+1t7ezgHgVCoVd/z4ceHnr732GgeA+93vfrfk2Lw/mZyc5HQ6HXfeeedxLMsKj/vFL37BAeCeeuqpJed+9tlnhZ+5XC6uoKCAu/TSS4Wf9fX1LTnvRz7yES4tLY0bGBjwez3kswiloYoGkRC++c1v+v39W9/6FgDglVdewSuvvAIASyoON910EwDg5ZdfBvBBr+3//M//LMny8LzyyitQq9X49re/veRYHMctyfqce+65qKysFP6+detWpKeno7e3N+Rr+b//+z/Mzc3hlltuWdIH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amoiISFBilbodDqpYa+urg4PDw/J6fDx8RmQSE5hYaHUdPj888+fsp+L77//njVr1pCWlkZUVBS1tbV8+umnPPjgg1x33XU88cQTALzwwgvEx8dLNdOnU92rjIwjkG3T4NJTFn7ixIlSQKw/WXitVktaWhre3t6dHtc1C+/v7y85HQM1S+OLL77gL3/5C2+//TaXXHLJgDzHcEC2Tf1HdjSGISdOnODcc88lISGB999//7T9cDoSnU7H7t27pYY9jUbDsmXLSEpKYtGiRRazESaTicOHD9Pa2kpCQoJFNRGDwdCpYc/FxUWqnfX393eIMS4oKGD58uVceOGFPPvss6eUge9as1paWsratWuprKzkxx9/JDIyktraWj755BMeeOABzjvvPPz9/fnf//7H0aNHGT169BCuXkbm9EC2TY5FzMJ/9dVXbNu2rV9ZeI1GQ1paGn5+fkydOtWiPdBoNJLISX19fac5Uo7qMUxJSWH9+vW8++67XHTRRQ655nBBtk32Izsaw4wTJ05wzjnnMHbsWN55551Om0x/JdhkesZoNLJv3z7J6aiqqmLhwoUkJyezbNky/P39USgUaLVacnNz0Wq1xMfH9ysDUldXJ23ugiBITkdgYKBNxvnYsWMsX76cSy65hKeffvqUdTIyMzPx8/Nj3LhxlJaWsm7dOo4ePcovv/zCmDFjaGho4IcffuD555/Hx8eH5557jqioqNNGJlBGZqiQbdPAY20Wvrm5mczMTIKCgpgyZYrV2XOdTicFxGpra6U5UmIzuS1Z+M8//5wbb7yR999/n9WrV/f78cMZ2TY5BtnRGGa8/fbbrFu3rsffyX8qx2MymcjKypKcjvz8fM4++2wWL17MBx98wJIlS9i4caPNMoFixEpMY+v1eoKDgwkJCSE4ONiq6x49epTly5ezdu1annzyyVNq0zLfyF944QU2b97MAw88wKpVq/Dz86OoqIhrr72WoqIifvnll07RIbHp7nROScvIDBaybRpczLPw27dvp729nWXLljFz5kyef/55tmzZwuLFi20u0TUajZ2y8E5OTlJ5lTVzpARBYOvWrdxyyy28//77p1yzs2ybHIfsaJzGbNmyRVJfiImJ4YUXXmDOnDlDvawhQxAECgoK+PDDD3nyySdpbm5m9uzZrF69muTkZCIjI+2WzW1paZGcjtbW1k4Nez2V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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# plot the two distributions as 3dbar subplots\n", "x, y = np.meshgrid(walk_pos, walk_pos)\n", "cmap = plt.get_cmap('jet') # Get desired colormap\n", "bosonic_max_height = np.max(bosonic_walk_probs.flatten())\n", "bosonic_min_height = np.min(bosonic_walk_probs.flatten())\n", "fermionic_max_height = np.max(fermionic_walk_probs.flatten())\n", "fermionic_min_height = np.min(fermionic_walk_probs.flatten())\n", "# scale each z to [0,1], and get their rgb values\n", "bosonic_rgba = [cmap((k-bosonic_min_height)/bosonic_max_height) if k!=0 else (0,0,0,0) for k in bosonic_walk_probs.flatten()]\n", "fermionic_rgba = [cmap((k-fermionic_min_height)/fermionic_max_height) if k!=0 else (0,0,0,0) for k in fermionic_walk_probs.flatten()]\n", "fig = plt.figure(figsize=(10, 16))\n", "ax = plt.subplot(1, 2, 1, projection='3d')\n", "ax.bar3d(x.flatten(), y.flatten(), np.zeros((2*steps+1)*(2*steps+1)), 1, 1, bosonic_walk_probs.flatten(), color=bosonic_rgba)\n", "ax.set_xlabel(\"position\")\n", "ax.set_ylabel(\"position\")\n", "ax.set_zlabel(\"probability\")\n", "ax.set_box_aspect(aspect=None, zoom=0.8)\n", "ax.set_title(\"bosonic\")\n", "ax = plt.subplot(1, 2, 2, projection='3d')\n", "ax.bar3d(x.flatten(), y.flatten(), np.zeros((2*steps+1)*(2*steps+1)), 1, 1, fermionic_walk_probs.flatten(), color=fermionic_rgba)\n", "ax.set_xlabel(\"position\")\n", "ax.set_ylabel(\"position\")\n", "ax.set_zlabel(\"probability\")\n", "ax.set_box_aspect(aspect=None, zoom=0.8)\n", "ax.set_title(\"fermionic\")\n", "plt.show()" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: docs/source/notebooks/VQA_Tutorial.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "1e8f0581", "metadata": {}, "source": [ "# Simple Variational Quantum Algorithm" ] }, { "cell_type": "markdown", "id": "25a047bb", "metadata": {}, "source": [ "In variational algorithms, the samples from a quantum circuit allow us to approximate an expectation value, which is then used to determine the value of a loss function. This loss function is chosen such that minimising it yields a solution to a given problem. By changing the values of the parameters in our quantum circuit, we can search for this minimum.\n", "\n", "We won't go into the details of variational algorithms. However, it may be useful to see how to perform such an optimisation with Perceval.\n", "\n", "We will use the library [scipy.optimise](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html#scipy.optimize.minimize).\n", "\n", "The following code solves the problem of finding a LO-Circuit which, given a Fock State $|1,1,1,1\\rangle$, maximises the probability of outputting $|4,0,0,0\\rangle$.\n", "The solution below works for an arbitrary $n$." ] }, { "cell_type": "code", "execution_count": 1, "id": "3e383966", "metadata": {}, "outputs": [], "source": [ "# Required imports\n", "import numpy as np\n", "from perceval import BasicState, BS, GenericInterferometer, SLOSBackend, Parameter, PS, Unitary, Matrix, pdisplay\n", "from random import random\n", "from scipy import optimize" ] }, { "cell_type": "code", "execution_count": 2, "id": "a8e4fe4a", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=phi0\n", "\n", "\n", "Φ=phi1\n", "\n", "\n", "Φ=phi2\n", "\n", "\n", "Φ=phi3\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=Φ_tr0\n", "Θ=theta0\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=Φ_tr1\n", "Θ=theta1\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=Φ_tr2\n", "Θ=theta2\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=Φ_tr3\n", "Θ=theta3\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=Φ_tr4\n", "Θ=theta4\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "Φ_tr=Φ_tr5\n", "Θ=theta5\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Data\n", "n = 4\n", "input_state = BasicState([1]*n)\n", "output_to_max = BasicState([n]+[0]*(n-1))\n", "backend = SLOSBackend()\n", "\n", "# We create a generic interferometer (variable parameters make it universal - i.e. it can implement any unitary transformation)\n", "circuit = GenericInterferometer(n,\n", " lambda i: BS(theta=Parameter(f\"theta{i}\"), phi_tr=Parameter(f\"phi_tr{i}\")),\n", " phase_shifter_fun_gen=lambda i: PS(phi=Parameter(f\"phi{i}\")))\n", "param_circuit = circuit.get_parameters()\n", "params_init = [random()*np.pi for _ in param_circuit]\n", "\n", "pdisplay(circuit)" ] }, { "cell_type": "code", "execution_count": 3, "id": "9d4968e6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The maximum probability is 0.09374999999784901\n" ] } ], "source": [ "# Define a loss function...\n", "def loss_function(params):\n", " for i, value in enumerate(params):\n", " param_circuit[i].set_value(value)\n", " backend.set_circuit(circuit)\n", " backend.set_input_state(input_state)\n", " return - backend.probability(output_to_max) # we want to maximise the prob, so we want to minimise the -prob\n", "\n", "# ... and run the optimisation\n", "o = optimize.minimize(loss_function, params_init, method=\"Powell\")\n", "\n", "# For n = 4, it is known that the probability should be 3/32 = 0.09375\n", "print(f\"The maximum probability is {-loss_function(o.x)}\")" ] }, { "cell_type": "code", "execution_count": 4, "id": "3aa08fb1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Using the Hadamard matrix gives 0.09374999999999999\n" ] } ], "source": [ "# The maximum can also be obtained with the Hadamard matrix :\n", "H4 = (1/2) * np.array([[1, 1, 1, 1], [1, -1, 1, -1], [1, 1, -1, -1], [1, -1, -1, 1]])\n", "backend.set_circuit(Unitary(Matrix(H4)))\n", "backend.set_input_state(input_state)\n", "print(f\"Using the Hadamard matrix gives {backend.probability(output_to_max)}\")" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Variational_Quantum_Eigensolver.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "39710952", "metadata": {}, "source": [ "# Variational Quantum Eigensolver" ] }, { "cell_type": "markdown", "id": "5e2598a1", "metadata": {}, "source": [ "We provide a Perceval implementation of original photonic Variational Quantum Eigensolver (VQE) from Peruzzo, A. et al. in [1].\n", "\n", "This notebook provides computations of ground-state molecular energies for the Hamiltonian given by the following papers:\n", "\n", "$\\bullet$ A variational eigenvalue solver on a photonic quantum processor [1].\n", " \n", "$\\bullet$ Scalable Quantum Simulation of Molecular Energies [2] .\n", " \n", "$\\bullet$ Computation of Molecular Spectra on a Quantum Processor with an Error-Resilient Algorithm [3]" ] }, { "cell_type": "markdown", "id": "14d8d0b4", "metadata": {}, "source": [ "Importing the necessary packages for the computations:" ] }, { "cell_type": "code", "execution_count": 1, "id": "1e59ecca", "metadata": {}, "outputs": [], "source": [ "from tqdm.auto import tqdm\n", "\n", "import perceval as pcvl\n", "\n", "import numpy as np\n", "from scipy.optimize import minimize\n", "import random\n", "import matplotlib.pyplot as plt\n", "simulator = pcvl.Simulator(pcvl.NaiveBackend())" ] }, { "cell_type": "markdown", "id": "111f0914", "metadata": {}, "source": [ "## Implementation in Perceval\n", "\n", "Here we reproduce on Perceval the 2-qubit circuit used in [1] for VQE.\n", "\n", "We use path encoded qubits.\n", "\n", "Modes 0 and 5 are auxiliary. 1$^\\text{st}$ qubit is path encoded in modes 1 and 2. 2$^\\text{and}$ qubit in modes 3 and 4." ] }, { "cell_type": "code", "execution_count": 2, "id": "22786406", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The circuit is :\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=φ1\n", "\n", "\n", "Φ=φ3\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=φ2\n", "\n", "\n", "Φ=φ4\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Φ=φ5\n", "\n", "\n", "Φ=φ7\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=φ6\n", "\n", "\n", "Φ=φ8\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#List of the parameters φ1,φ2,...,φ8\n", "List_Parameters=[]\n", "\n", "# VQE is a 6 optical mode circuit\n", "VQE=pcvl.Circuit(6)\n", "\n", "VQE.add((1,2), pcvl.BS())\n", "VQE.add((3,4), pcvl.BS())\n", "List_Parameters.append(pcvl.Parameter(\"φ1\"))\n", "VQE.add((2,),pcvl.PS(phi=List_Parameters[-1]))\n", "List_Parameters.append(pcvl.Parameter(\"φ3\"))\n", "VQE.add((4,),pcvl.PS(phi=List_Parameters[-1]))\n", "VQE.add((1,2), pcvl.BS())\n", "VQE.add((3,4), pcvl.BS())\n", "List_Parameters.append(pcvl.Parameter(\"φ2\"))\n", "VQE.add((2,),pcvl.PS(phi=List_Parameters[-1]))\n", "List_Parameters.append(pcvl.Parameter(\"φ4\"))\n", "VQE.add((4,),pcvl.PS(phi=List_Parameters[-1]))\n", "\n", "\n", "# CNOT ( Post-selected with a success probability of 1/9) \n", "VQE.add([0,1,2,3,4,5], pcvl.PERM([0,1,2,3,4,5]))#Identity PERM (permutation) for the purpose of drawing a nice circuit\n", "VQE.add((3,4), pcvl.BS())\n", "VQE.add([0,1,2,3,4,5], pcvl.PERM([0,1,2,3,4,5]))#Identity PERM (permutation) for the same purpose\n", "VQE.add((0,1), pcvl.BS(pcvl.BS.r_to_theta(1/3)))\n", "VQE.add((2,3), pcvl.BS(pcvl.BS.r_to_theta(1/3)))\n", "VQE.add((4,5), pcvl.BS(pcvl.BS.r_to_theta(1/3)))\n", "VQE.add([0,1,2,3,4,5], pcvl.PERM([0,1,2,3,4,5]))#Identity PERM (permutation) for the same purpose\n", "VQE.add((3,4), pcvl.BS())\n", "VQE.add([0,1,2,3,4,5], pcvl.PERM([0,1,2,3,4,5]))#Identity PERM (permutation) for the same purpose\n", "\n", "List_Parameters.append(pcvl.Parameter(\"φ5\"))\n", "VQE.add((2,),pcvl.PS(phi=List_Parameters[-1]))\n", "List_Parameters.append(pcvl.Parameter(\"φ7\"))\n", "VQE.add((4,),pcvl.PS(phi=List_Parameters[-1]))\n", "VQE.add((1,2), pcvl.BS())\n", "VQE.add((3,4), pcvl.BS())\n", "List_Parameters.append(pcvl.Parameter(\"φ6\"))\n", "VQE.add((2,),pcvl.PS(phi=List_Parameters[-1]))\n", "List_Parameters.append(pcvl.Parameter(\"φ8\"))\n", "VQE.add((4,),pcvl.PS(phi=List_Parameters[-1]))\n", "VQE.add((1,2), pcvl.BS())\n", "VQE.add((3,4), pcvl.BS())\n", "\n", "# Mode 0 and 5 are auxiliary.\n", "#1st qubit is path encoded in modes 1 & 2\n", "#2nd qubit in 3 & 4\n", "\n", "print(\"The circuit is :\")\n", "pcvl.pdisplay(VQE)" ] }, { "cell_type": "markdown", "id": "91a1daa5", "metadata": {}, "source": [ "## Logical input and output states\n", "\n", "Logical states are path encoded on the Fock States." ] }, { "cell_type": "code", "execution_count": 3, "id": "09dba52d", "metadata": {}, "outputs": [], "source": [ "#Input states of the photonic circuit\n", "input_states = {\n", " pcvl.BasicState([0,1,0,1,0,0]):\"|00>\"}\n", "\n", "#Outputs in the computational basis\n", "output_states = {\n", " pcvl.BasicState([0,1,0,1,0,0]):\"|00>\",\n", " pcvl.BasicState([0,1,0,0,1,0]):\"|01>\",\n", " pcvl.BasicState([0,0,1,1,0,0]):\"|10>\",\n", " pcvl.BasicState([0,0,1,0,1,0]):\"|11>\"}" ] }, { "cell_type": "markdown", "id": "d2d1cf4f", "metadata": {}, "source": [ "## Loss function for the variational algorithm\n", "\n", "For a given state $|\\psi \\rangle$ the Rayleigh-Ritz quotient is given by:\n", "$$ \\frac{\\langle \\psi | \\mathcal{H} | \\psi \\rangle}{\\langle \\psi |\\psi \\rangle},$$\n", "This is computed in Perceval by explicitly computing $|\\psi \\rangle$ ( which is the output of the simulated photonic circuit).\n", "Determining the smallest/ground state eigenvalue is done through a variational approach with Rayleigh-Ritz quotient as the loss function.\n", "\n", "Using a classical optimisation algorithm, and the simulations given by Perceval we find the eigenvector which minimises the Rayleigh-Ritz quotient together with its associated eigenvalue ( the ground state eigenvalue), which reprensents the solution of the VQE.\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "db3c68e5", "metadata": {}, "outputs": [], "source": [ "\n", "def minimize_loss(lp=None): \n", " # Updating the parameters on the chip\n", " for idx, p in enumerate(lp): \n", " List_Parameters[idx].set_value(p)\n", " \n", " #Simulation, Quantum processing part of the VQE\n", " simulator.set_circuit(VQE)\n", " \n", " # Collecting the output state of the circuit \n", " psi = [] \n", " for input_state in input_states:\n", " for output_state in output_states: #|00>,|01>,|10>,|11>\n", " psi.append(simulator.prob_amplitude(input_state,output_state))\n", " \n", " #Evaluating the mean value of the Hamiltonian. # The Hamiltonians H is defined in the following block\n", " psi_prime=np.dot(H[R][1],psi)\n", " loss = np.real(sum(sum(np.conjugate(psi)*np.array(psi_prime[0]))))/(sum([i*np.conjugate(i) for i in psi]))\n", " loss=np.real(loss)\n", " \n", " tq.set_description('%g / %g loss function=%g' % (R, len(H), loss))\n", " return(loss)\n" ] }, { "cell_type": "markdown", "id": "0f7d26a2", "metadata": {}, "source": [ "## Hamiltonians for VQE\n", "\n", "In this section we define the 3 Hamiltonians given by the 3 papers [1-3]\n", "\n", "Perceval can compute the state vector, which is the output of the photonic quantum circuit, explicitly.\n", "This eliminates the need for using algorithms to estimate the expectation value.\n", "\n", "A Hamiltonian can be written as :\n", "$$\\mathcal{H}= \\sum_{\\sigma }h_{\\sigma}\\sigma$$\n", "where $h_{\\sigma} \\in \\mathbb{R}$ and $\\sigma$ are pauli operators." ] }, { "cell_type": "markdown", "id": "97e78feb", "metadata": {}, "source": [ "### Hamiltonian n°1 [\\[1\\]](https://www.nature.com/articles/ncomms5213)\n", "\n", "Hamiltonian coefficients on the [supplementary paper](https://static-content.springer.com/esm/art%3A10.1038%2Fncomms5213/MediaObjects/41467_2014_BFncomms5213_MOESM1050_ESM.pdf):\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "b3f89652", "metadata": {}, "outputs": [], "source": [ "Hamiltonian_elem = np.array([[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]], #00\n", " [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], #II\n", " [[0,1,0,0],[1,0,0,0],[0,0,0,1],[0,0,1,0]], #IX\n", " [[1,0,0,0],[0,-1,0,0],[0,0,1,0],[0,0,0,-1]], #IZ\n", " [[0,0,1,0],[0,0,0,1],[1,0,0,0],[0,1,0,0]], #XI\n", " [[0,0,0,1],[0,0,1,0],[0,1,0,0],[1,0,0,0]], #XX\n", " [[0,0,1,0],[0,0,0,-1],[1,0,0,0],[0,-1,0,0]], #XZ\n", " [[1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,-1]], #ZI\n", " [[0,1,0,0],[1,0,0,0],[0,0,0,-1],[0,0,-1,0]], #ZX\n", " [[1,0,0,0],[0,-1,0,0],[0,0,-1,0],[0,0,0,1]]]) #ZZ\n", "\n", "\n", "Hamiltonian_coef = np.matrix(\n", "# [R, II, IX, IZ, XI, XX, XZ, ZI, ZX, ZZ] \n", "[[0.05,33.9557,-0.1515,-2.4784,-0.1515,0.1412,0.1515,-2.4784,0.1515,0.2746],\n", "[0.1,13.3605,-0.1626,-2.4368,-0.1626,0.2097,0.1626,-2.4368,0.1626,0.2081],\n", "[0.15,6.8232,-0.1537,-2.3801,-0.1537,0.2680,0.1537,-2.3801,0.1537,0.1512],\n", "[0.2,3.6330,-0.1405,-2.2899,-0.1405,0.3027,0.1405,-2.2899,0.1405,0.1176],\n", "[0.25,1.7012,-0.1324,-2.1683,-0.1324,0.3211,0.1324,-2.1683,0.1324,0.1010],\n", "[0.3,0.3821,-0.1306,-2.0305,-0.1306,0.3303,0.1306,-2.0305,0.1306,0.0943],\n", "[0.35,-0.5810,-0.1335,-1.8905,-0.1335,0.3344,0.1335,-1.8905,0.1335,0.0936],\n", "[0.4,-1.3119,-0.1396,-1.7568,-0.1396,0.3352,0.1396,-1.7568,0.1396,0.0969],\n", "[0.45,-1.8796,-0.1477,-1.6339,-0.1477,0.3339,0.1477,-1.6339,0.1477,0.1030],\n", "[0.5,-2.3275,-0.1570,-1.5236,-0.1570,0.3309,0.1570,-1.5236,0.1570,0.1115],\n", "[0.55,-2.6844,-0.1669,-1.4264,-0.1669,0.3264,0.1669,-1.4264,0.1669,0.1218],\n", "[0.6,-2.9708,-0.1770,-1.3418,-0.1770,0.3206,0.1770,-1.3418,0.1770,0.1339],\n", "[0.65,-3.2020,-0.1871,-1.2691,-0.1871,0.3135,0.1871,-1.2691,0.1871,0.1475],\n", "[0.7,-3.3893,-0.1968,-1.2073,-0.1968,0.3052,0.1968,-1.2073,0.1968,0.1626],\n", "[0.75,-3.5417,-0.2060,-1.1552,-0.2060,0.2958,0.2060,-1.1552,0.2060,0.1791],\n", "[0.8,-3.6660,-0.2145,-1.1117,-0.2145,0.2853,0.2145,-1.1117,0.2145,0.1968],\n", "[0.85,-3.7675,-0.2222,-1.0758,-0.2222,0.2738,0.2222,-1.0758,0.2222,0.2157],\n", "[0.9,-3.8505,-0.2288,-1.0466,-0.2288,0.2613,0.2288,-1.0466,0.2288,0.2356],\n", "[0.95,-3.9183,-0.2343,-1.0233,-0.2343,0.2481,0.2343,-1.0233,0.2343,0.2564],\n", "[1,-3.9734,-0.2385,-1.0052,-0.2385,0.2343,0.2385,-1.0052,0.2385,0.2779],\n", "[1.05,-4.0180,-0.2414,-0.9916,-0.2414,0.2199,0.2414,-0.9916,0.2414,0.3000],\n", "[1.1,-4.0539,-0.2430,-0.9820,-0.2430,0.2053,0.2430,-0.9820,0.2430,0.3225],\n", "[1.15,-4.0825,-0.2431,-0.9758,-0.2431,0.1904,0.2431,-0.9758,0.2431,0.3451],\n", "[1.2,-4.1050,-0.2418,-0.9725,-0.2418,0.1756,0.2418,-0.9725,0.2418,0.3678],\n", "[1.25,-4.1224,-0.2392,-0.9716,-0.2392,0.1610,0.2392,-0.9716,0.2392,0.3902],\n", "[1.3,-4.1356,-0.2353,-0.9728,-0.2353,0.1466,0.2353,-0.9728,0.2353,0.4123],\n", "[1.35,-4.1454,-0.2301,-0.9757,-0.2301,0.1327,0.2301,-0.9757,0.2301,0.4339],\n", "[1.4,-4.1523,-0.2239,-0.9798,-0.2239,0.1194,0.2239,-0.9798,0.2239,0.4549],\n", "[1.45,-4.1568,-0.2167,-0.9850,-0.2167,0.1068,0.2167,-0.9850,0.2167,0.4751],\n", "[1.5,-4.1594,-0.2086,-0.9910,-0.2086,0.0948,0.2086,-0.9910,0.2086,0.4945],\n", "[1.55,-4.1605,-0.1998,-0.9975,-0.1998,0.0837,0.1998,-0.9975,0.1998,0.5129],\n", "[1.6,-4.1602,-0.1905,-1.0045,-0.1905,0.0734,0.1905,-1.0045,0.1905,0.5304],\n", "[1.65,-4.1589,-0.1807,-1.0116,-0.1807,0.0640,0.1807,-1.0116,0.1807,0.5468],\n", "[1.7,-4.1568,-0.1707,-1.0189,-0.1707,0.0555,0.1707,-1.0189,0.1707,0.5622],\n", "[1.75,-4.1540,-0.1605,-1.0262,-0.1605,0.0479,0.1605,-1.0262,0.1605,0.5766],\n", "[1.8,-4.1508,-0.1503,-1.0334,-0.1503,0.0410,0.1503,-1.0334,0.1503,0.5899],\n", "[1.85,-4.1471,-0.1403,-1.0404,-0.1403,0.0350,0.1403,-1.0404,0.1403,0.6023],\n", "[1.9,-4.1431,-0.1305,-1.0473,-0.1305,0.0297,0.1305,-1.0473,0.1305,0.6138],\n", "[1.95,-4.1390,-0.1210,-1.0540,-0.1210,0.0251,0.1210,-1.0540,0.1210,0.6244],\n", "[2,-4.1347,-0.1119,-1.0605,-0.1119,0.0212,0.1119,-1.0605,0.1119,0.6342],\n", "[2.05,-4.1303,-0.1031,-1.0667,-0.1031,0.0178,0.1031,-1.0667,0.1031,0.6432],\n", "[2.1,-4.1258,-0.0949,-1.0727,-0.0949,0.0148,0.0949,-1.0727,0.0949,0.6516],\n", "[2.15,-4.1214,-0.0871,-1.0785,-0.0871,0.0124,0.0871,-1.0785,0.0871,0.6594],\n", "[2.2,-4.1169,-0.0797,-1.0840,-0.0797,0.0103,0.0797,-1.0840,0.0797,0.6666],\n", "[2.25,-4.1125,-0.0729,-1.0893,-0.0729,0.0085,0.0729,-1.0893,0.0729,0.6733],\n", "[2.3,-4.1082,-0.0665,-1.0944,-0.0665,0.0070,0.0665,-1.0944,0.0665,0.6796],\n", "[2.35,-4.1040,-0.0606,-1.0993,-0.0606,0.0058,0.0606,-1.0993,0.0606,0.6854],\n", "[2.4,-4.0998,-0.0551,-1.1040,-0.0551,0.0047,0.0551,-1.1040,0.0551,0.6909],\n", "[2.45,-4.0957,-0.0500,-1.1085,-0.0500,0.0039,0.0500,-1.1085,0.0500,0.6961],\n", "[2.5,-4.0918,-0.0454,-1.1128,-0.0454,0.0032,0.0454,-1.1128,0.0454,0.7010],\n", "[2.55,-4.0879,-0.0411,-1.1170,-0.0411,0.0026,0.0411,-1.1170,0.0411,0.7056],\n", "[2.6,-4.0841,-0.0371,-1.1210,-0.0371,0.0021,0.0371,-1.1210,0.0371,0.7099],\n", "[2.65,-4.0805,-0.0335,-1.1248,-0.0335,0.0017,0.0335,-1.1248,0.0335,0.7141],\n", "[2.7,-4.0769,-0.0303,-1.1285,-0.0303,0.0014,0.0303,-1.1285,0.0303,0.7181],\n", "[2.75,-4.0735,-0.0273,-1.1321,-0.0273,0.0011,0.0273,-1.1321,0.0273,0.7218],\n", "[2.8,-4.0701,-0.0245,-1.1356,-0.0245,0.0009,0.0245,-1.1356,0.0245,0.7254],\n", "[2.85,-4.0669,-0.0221,-1.1389,-0.0221,0.0007,0.0221,-1.1389,0.0221,0.7289],\n", "[2.9,-4.0638,-0.0198,-1.1421,-0.0198,0.0006,0.0198,-1.1421,0.0198,0.7322],\n", "[2.95,-4.0607,-0.0178,-1.1452,-0.0178,0.0005,0.0178,-1.1452,0.0178,0.7354],\n", "[3,-4.0578,-0.0159,-1.1482,-0.0159,0.0004,0.0159,-1.1482,0.0159,0.7385],\n", "[3.05,-4.0549,-0.0142,-1.1511,-0.0142,0.0003,0.0142,-1.1511,0.0142,0.7414],\n", "[3.1,-4.0521,-0.0127,-1.1539,-0.0127,0.0002,0.0127,-1.1539,0.0127,0.7443],\n", "[3.15,-4.0494,-0.0114,-1.1566,-0.0114,0.0002,0.0114,-1.1566,0.0114,0.7470],\n", "[3.2,-4.0468,-0.0101,-1.1592,-0.0101,0.0001,0.0101,-1.1592,0.0101,0.7497],\n", "[3.25,-4.0443,-0.0090,-1.1618,-0.0090,0.0001,0.0090,-1.1618,0.0090,0.7522],\n", "[3.3,-4.0418,-0.0081,-1.1643,-0.0081,0.0001,0.0081,-1.1643,0.0081,0.7547],\n", "[3.35,-4.0394,-0.0072,-1.1666,-0.0072,0.0001,0.0072,-1.1666,0.0072,0.7571],\n", "[3.4,-4.0371,-0.0064,-1.1690,-0.0064,0.0001,0.0064,-1.1690,0.0064,0.7595],\n", "[3.45,-4.0349,-0.0056,-1.1712,-0.0056,0.0000,0.0056,-1.1712,0.0056,0.7617],\n", "[3.5,-4.0327,-0.0050,-1.1734,-0.0050,0.0000,0.0050,-1.1734,0.0050,0.7639],\n", "[3.55,-4.0306,-0.0044,-1.1756,-0.0044,0.0000,0.0044,-1.1756,0.0044,0.7661],\n", "[3.6,-4.0285,-0.0039,-1.1776,-0.0039,0.0000,0.0039,-1.1776,0.0039,0.7681],\n", "[3.65,-4.0265,-0.0035,-1.1796,-0.0035,0.0000,0.0035,-1.1796,0.0035,0.7702],\n", "[3.7,-4.0245,-0.0030,-1.1816,-0.0030,0.0000,0.0030,-1.1816,0.0030,0.7721],\n", "[3.75,-4.0226,-0.0027,-1.1835,-0.0027,0.0000,0.0027,-1.1835,0.0027,0.7740],\n", "[3.8,-4.0208,-0.0024,-1.1854,-0.0024,0.0000,0.0024,-1.1854,0.0024,0.7759],\n", "[3.85,-4.0190,-0.0021,-1.1872,-0.0021,0.0000,0.0021,-1.1872,0.0021,0.7777],\n", "[3.9,-4.0172,-0.0018,-1.1889,-0.0018,0.0000,0.0018,-1.1889,0.0018,0.7795],\n", "[3.95,-4.0155,-0.0016,-1.1906,-0.0016,0.0000,0.0016,-1.1906,0.0016,0.7812]]\n", ")\n", "\n", "\n", "# Building the Hamiltonian H[0] = Radius, H[1] = H(Radius)\n", "H1 = []\n", "(n,m) = Hamiltonian_coef.shape\n", "for i in range(n): # i = Radius\n", " h_0 = 1.0 * np.matrix(Hamiltonian_elem[0])\n", " for j in range(1, m):\n", " h_0 += Hamiltonian_coef[i,j] * np.matrix(Hamiltonian_elem[j])\n", " H1.append([Hamiltonian_coef[i,0], h_0])" ] }, { "cell_type": "markdown", "id": "913d4aea", "metadata": {}, "source": [ "### Hamiltonian n°2 [\\[2\\]](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.73.58)\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "87f168f2", "metadata": {}, "outputs": [], "source": [ "Hamiltonian_elem = np.array([[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]], #00\n", " [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], #II\n", " [[1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,-1]], #ZI\n", " [[1,0,0,0],[0,-1,0,0],[0,0,1,0],[0,0,0,-1]], #IZ\n", " [[1,0,0,0],[0,-1,0,0],[0,0,-1,0],[0,0,0,1]], #ZZ\n", " [[0,0,0,1],[0,0,1,0],[0,1,0,0],[1,0,0,0]], #XX\n", " [[0,0,0,-1],[0,0,1,0],[0,1,0,0],[-1,0,0,0]]]) #YY\n", " \n", "\n", "Hamiltonian_coef = np.matrix(\n", "# [R, II, ZI, IZ, ZZ, XX, YY] \n", "[[0.20,2.8489,0.5678,-1.4508,0.6799,0.0791,0.0791],\n", "[0.25,2.1868,0.5449,-1.2870,0.6719,0.0798,0.0798],\n", "[0.30,1.7252,0.5215,-1.1458,0.6631,0.0806,0.0806],\n", "[0.35,1.3827,0.4982,-1.0226,0.6537,0.0815,0.0815],\n", "[0.40,1.1182,0.4754,-0.9145,0.6438,0.0825,0.0825],\n", "[0.45,0.9083,0.4534,-0.8194,0.6336,0.0835,0.0835],\n", "[0.50,0.7381,0.4325,-0.7355,0.6233,0.0846,0.0846],\n", "[0.55,0.5979,0.4125,-0.6612,0.6129,0.0858,0.0858],\n", "[0.60,0.4808,0.3937,-0.5950,0.6025,0.0870,0.0870],\n", "[0.65,0.3819,0.3760,-0.5358,0.5921,0.0883,0.0883],\n", "[0.70,0.2976,0.3593,-0.4826,0.5818,0.0896,0.0896],\n", "[0.75,0.2252,0.3435,-0.4347,0.5716,0.0910,0.0910],\n", "[0.80,0.1626,0.3288,-0.3915,0.5616,0.0925,0.0925],\n", "[0.85,0.1083,0.3149,-0.3523,0.5518,0.0939,0.0939],\n", "[0.90,0.0609,0.3018,-0.3168,0.5421,0.0954,0.0954],\n", "[0.95,0.0193,0.2895,-0.2845,0.5327,0.0970,0.0970],\n", "[1.00,-0.0172,0.2779,-0.2550,0.5235,0.0986,0.0986],\n", "[1.05,-0.0493,0.2669,-0.2282,0.5146,0.1002,0.1002],\n", "[1.10,-0.0778,0.2565,-0.2036,0.5059,0.1018,0.1018],\n", "[1.15,-0.1029,0.2467,-0.1810,0.4974,0.1034,0.1034],\n", "[1.20,-0.1253,0.2374,-0.1603,0.4892,0.1050,0.1050],\n", "[1.25,-0.1452,0.2286,-0.1413,0.4812,0.1067,0.1067],\n", "[1.30,-0.1629,0.2203,-0.1238,0.4735,0.1083,0.1083],\n", "[1.35,-0.1786,0.2123,-0.1077,0.4660,0.1100,0.1100],\n", "[1.40,-0.1927,0.2048,-0.0929,0.4588,0.1116,0.1116],\n", "[1.45,-0.2053,0.1976,-0.0792,0.4518,0.1133,0.1133],\n", "[1.50,-0.2165,0.1908,-0.0666,0.4451,0.1149,0.1149],\n", "[1.55,-0.2265,0.1843,-0.0549,0.4386,0.1165,0.1165],\n", "[1.60,-0.2355,0.1782,-0.0442,0.4323,0.1181,0.1181],\n", "[1.65,-0.2436,0.1723,-0.0342,0.4262,0.1196,0.1196],\n", "[1.70,-0.2508,0.1667,-0.0251,0.4204,0.1211,0.1211],\n", "[1.75,-0.2573,0.1615,-0.0166,0.4148,0.1226,0.1226],\n", "[1.80,-0.2632,0.1565,-0.0088,0.4094,0.1241,0.1241],\n", "[1.85,-0.2684,0.1517,-0.0015,0.4042,0.1256,0.1256],\n", "[1.90,-0.2731,0.1472,0.0052,0.3992,0.1270,0.1270],\n", "[1.95,-0.2774,0.1430,0.0114,0.3944,0.1284,0.1284],\n", "[2.00,-0.2812,0.1390,0.0171,0.3898,0.1297,0.1297],\n", "[2.05,-0.2847,0.1352,0.0223,0.3853,0.1310,0.1310],\n", "[2.10,-0.2879,0.1316,0.0272,0.3811,0.1323,0.1323],\n", "[2.15,-0.2908,0.1282,0.0317,0.3769,0.1335,0.1335],\n", "[2.20,-0.2934,0.1251,0.0359,0.3730,0.1347,0.1347],\n", "[2.25,-0.2958,0.1221,0.0397,0.3692,0.1359,0.1359],\n", "[2.30,-0.2980,0.1193,0.0432,0.3655,0.1370,0.1370],\n", "[2.35,-0.3000,0.1167,0.0465,0.3620,0.1381,0.1381],\n", "[2.40,-0.3018,0.1142,0.0495,0.3586,0.1392,0.1392],\n", "[2.45,-0.3035,0.1119,0.0523,0.3553,0.1402,0.1402],\n", "[2.50,-0.3051,0.1098,0.0549,0.3521,0.1412,0.1412],\n", "[2.55,-0.3066,0.1078,0.0572,0.3491,0.1422,0.1422],\n", "[2.60,-0.3079,0.1059,0.0594,0.3461,0.1432,0.1432],\n", "[2.65,-0.3092,0.1042,0.0614,0.3433,0.1441,0.1441],\n", "[2.70,-0.3104,0.1026,0.0632,0.3406,0.1450,0.1450],\n", "[2.75,-0.3115,0.1011,0.0649,0.3379,0.1458,0.1458],\n", "[2.80,-0.3125,0.0997,0.0665,0.3354,0.1467,0.1467],\n", "[2.85,-0.3135,0.0984,0.0679,0.3329,0.1475,0.1475]]\n", "\n", ")\n", "\n", "\n", "# Building the Hamiltonian H[0] = Radius, H[1] = H(Radius)\n", "H2 = []\n", "(n,m) = Hamiltonian_coef.shape\n", "for i in range(n): # i = Radius\n", " h_0 = 1.0 * np.matrix(Hamiltonian_elem[0])\n", " for j in range(1, m):\n", " h_0 += Hamiltonian_coef[i,j]*np.matrix(Hamiltonian_elem[j])\n", " H2.append([Hamiltonian_coef[i,0], h_0])" ] }, { "cell_type": "markdown", "id": "920b16b4", "metadata": {}, "source": [ "### Hamiltonian n°3 [\\[3\\]](https://journals.aps.org/prx/abstract/10.1103/PhysRevX.8.011021)\n", "\n", "Hamiltonian coefficients on the [supplementary material](https://journals.aps.org/prx/supplemental/10.1103/PhysRevX.8.011021/Supplementary.pdf)." ] }, { "cell_type": "code", "execution_count": 7, "id": "1a6576f0", "metadata": {}, "outputs": [], "source": [ "Hamiltonian_elem = np.array([[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]], #00\n", " [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], #II\n", " [[1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,-1]], #ZI\n", " [[0,0,0,1],[0,0,1,0],[0,1,0,0],[1,0,0,0]], #XX\n", " [[1,0,0,0],[0,-1,0,0],[0,0,1,0],[0,0,0,-1]], #IZ\n", " [[1,0,0,0],[0,-1,0,0],[0,0,-1,0],[0,0,0,1]]]) #ZZ\n", "\n", " \n", "Hamiltonian_coef = np.matrix(\n", "# [R, II, ZI, XX, IZ, ZZ]\n", "[[0.05, 1.00777E+01 ,-1.05533E+00 ,1.55708*10**(-1) ,-1.05533E+00 ,1.39333*10**(-2)],\n", "[0.10, 4.75665E+00 ,-1.02731E+00 ,1.56170*10**(-1) ,-1.02731E+00 ,1.38667*10**(-2)],\n", "[0.15, 2.94817E+00 ,-9.84234*10**(-1) ,1.56930*10**(-1) ,-9.84234*10**(-1) ,1.37610*10**(-2)],\n", "[0.20, 2.01153E+00 ,-9.30489*10**(-1) ,1.57973*10**(-1) ,-9.30489*10**(-1) ,1.36238*10**(-2)],\n", "[0.25, 1.42283E+00 ,-8.70646*10**(-1) ,1.59277*10**(-1) ,-8.70646*10**(-1) ,1.34635*10**(-2)],\n", "[0.30, 1.01018E+00 ,-8.08649*10**(-1) ,1.60818*10**(-1) ,-8.08649*10**(-1) ,1.32880*10**(-2)],\n", "[0.35, 7.01273*10**(-1) ,-7.47416*10**(-1) ,1.62573*10**(-1) ,-7.47416*10**(-1) ,1.31036*10**(-2)],\n", "[0.40, 4.60364*10**(-1) ,-6.88819*10**(-1) ,1.64515*10**(-1) ,-6.88819*10**(-1) ,1.29140*10**(-2)],\n", "[0.45, 2.67547*10**(-1) ,-6.33890*10**(-1) ,1.66621*10**(-1) ,-6.33890*10**(-1) ,1.27192*10**(-2)],\n", "[0.50, 1.10647*10**(-1) ,-5.83080*10**(-1) ,1.68870*10**(-1) ,-5.83080*10**(-1) ,1.25165*10**(-2)],\n", "[0.55, -1.83734*10**(-2), -5.36489*10**(-1), 1.71244*10**(-1), -5.36489*10**(-1), 1.23003*10**(-2)],\n", "[0.65, -2.13932*10**(-1), -4.55433*10**(-1), 1.76318*10**(-1), -4.55433*10**(-1), 1.18019*10**(-2)],\n", "[0.75, -3.49833*10**(-1), -3.88748*10**(-1), 1.81771*10**(-1), -3.88748*10**(-1), 1.11772*10**(-2)],\n", "[0.85, -4.45424*10**(-1), -3.33747*10**(-1), 1.87562*10**(-1), -3.33747*10**(-1), 1.04061*10**(-2)],\n", "[0.95, -5.13548*10**(-1), -2.87796*10**(-1), 1.93650*10**(-1), -2.87796*10**(-1), 9.50345*10**(-3)],\n", "[1.05, -5.62600*10**(-1), -2.48783*10**(-1), 1.99984*10**(-1), -2.48783*10**(-1), 8.50998*10**(-3)],\n", "[1.15, -5.97973*10**(-1), -2.15234*10**(-1), 2.06495*10**(-1), -2.15234*10**(-1), 7.47722*10**(-3)],\n", "[1.25, -6.23223*10**(-1), -1.86173*10**(-1), 2.13102*10**(-1), -1.86173*10**(-1), 6.45563*10**(-3)],\n", "[1.35, -6.40837*10**(-1), -1.60926*10**(-1), 2.19727*10**(-1), -1.60926*10**(-1), 5.48623*10**(-3)],\n", "[1.45, -6.52661*10**(-1), -1.38977*10**(-1), 2.26294*10**(-1), -1.38977*10**(-1), 4.59760*10**(-3)],\n", "[1.55, -6.60117*10**(-1), -1.19894*10**(-1), 2.32740*10**(-1), -1.19894*10**(-1), 3.80558*10**(-3)],\n", "[1.65, -6.64309*10**(-1), -1.03305*10**(-1), 2.39014*10**(-1), -1.03305*10**(-1), 3.11545*10**(-3)],\n", "[1.75, -6.66092*10**(-1), -8.88906*10**(-2), 2.45075*10**(-1), -8.88906*10**(-2), 2.52480*10**(-3)],\n", "[1.85, -6.66126*10**(-1), -7.63712*10**(-2), 2.50896*10**(-1), -7.63712*10**(-2), 2.02647*10**(-3)],\n", "[1.95, -6.64916*10**(-1), -6.55065*10**(-2), 2.56458*10**(-1), -6.55065*10**(-2), 1.61100*10**(-3)],\n", "[2.05, -6.62844*10**(-1), -5.60866*10**(-2), 2.61750*10**(-1), -5.60866*10**(-2), 1.26812*10**(-3)],\n", "[2.15, -6.60199*10**(-1), -4.79275*10**(-2), 2.66768*10**(-1), -4.79275*10**(-2), 9.88000*10**(-4)],\n", "[2.25, -6.57196*10**(-1), -4.08672*10**(-2), 2.71512*10**(-1), -4.08672*10**(-2), 7.61425*10**(-4)],\n", "[2.35, -6.53992*10**(-1), -3.47636*10**(-2), 2.75986*10**(-1), -3.47636*10**(-2), 5.80225*10**(-4)],\n", "[2.45, -6.50702*10**(-1), -2.94924*10**(-2), 2.80199*10**(-1), -2.94924*10**(-2), 4.36875*10**(-4)],\n", "[2.55, -6.47408*10**(-1), -2.49459*10**(-2), 2.84160*10**(-1), -2.49459*10**(-2), 3.25025*10**(-4)],\n", "[2.65, -6.44165*10**(-1), -2.10309*10**(-2), 2.87881*10**(-1), -2.10309*10**(-2), 2.38800*10**(-4)],\n", "[2.75, -6.41011*10**(-1), -1.76672*10**(-2), 2.91376*10**(-1), -1.76672*10**(-2), 1.73300*10**(-4)],\n", "[2.85, -6.37971*10**(-1), -1.47853*10**(-2), 2.94658*10**(-1), -1.47853*10**(-2), 1.24200*10**(-4)],\n", "[2.95, -6.35058*10**(-1), -1.23246*10**(-2), 2.97741*10**(-1), -1.23246*10**(-2), 8.78750*10**(-5)],\n", "[3.05, -6.32279*10**(-1), -1.02318*10**(-2), 3.00638*10**(-1), -1.02317*10**(-2), 6.14500*10**(-5)],\n", "[3.15, -6.29635*10**(-1), -8.45958*10**(-3), 3.03362*10**(-1), -8.45958*10**(-3), 4.24250*10**(-5)],\n", "[3.25, -6.27126*10**(-1), -6.96585*10**(-3), 3.05927*10**(-1), -6.96585*10**(-3), 2.89500*10**(-5)],\n", "[3.35, -6.24746*10**(-1), -5.71280*10**(-3), 3.08344*10**(-1), -5.71280*10**(-3), 1.95500*10**(-5)],\n", "[3.45, -6.22491*10**(-1), -4.66670*10**(-3), 3.10625*10**(-1), -4.66670*10**(-3), 1.30500*10**(-5)],\n", "[3.55, -6.20353*10**(-1), -3.79743*10**(-3), 3.12780*10**(-1), -3.79743*10**(-3), 8.57500*10**(-6)],\n", "[3.65, -6.18325*10**(-1), -3.07840*10**(-3), 3.14819*10**(-1), -3.07840*10**(-3), 5.60000*10**(-6)],\n", "[3.75, -6.16401*10**(-1), -2.48625*10**(-3), 3.16750*10**(-1), -2.48625*10**(-3), 3.60000*10**(-6)],\n", "[3.85, -6.14575*10**(-1), -2.00063*10**(-3), 3.18581*10**(-1), -2.00062*10**(-3), 2.27500*10**(-6)],\n", "[3.95, -6.12839*10**(-1), -1.60393*10**(-3), 3.20320*10**(-1), -1.60392*10**(-3), 1.42500*10**(-6)]] )\n", " \n", "# Building the Hamiltonian H[0]= Radius, H[1]=H(Radius)\n", "H3 = []\n", "(n,m) = Hamiltonian_coef.shape\n", "for i in range(n): # i = Radius\n", " h_0 = 1.0 * np.matrix(Hamiltonian_elem[0])\n", " for j in range(1,m):\n", " h_0 += Hamiltonian_coef[i,j]*np.matrix(Hamiltonian_elem[j])\n", " H3.append([Hamiltonian_coef[i,0], h_0])" ] }, { "cell_type": "markdown", "id": "b95733d2", "metadata": {}, "source": [ "## Varational quantum eigenvalue solver simulation\n", "\n", "We use the Nelder-Mead minimisation algorithm [4] from the `scipy.optimize` package.\n", "\n" ] }, { "cell_type": "markdown", "id": "c8096d2c", "metadata": {}, "source": [ "### Simulation n°1 " ] }, { "cell_type": "code", "execution_count": 8, "id": "29c10704", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "211267153d9241369b1e816ab553b0fe", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Minimizing...: 0it [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "tq = tqdm(desc='Minimizing...') #Displaying progress bar\n", "radius1=[]\n", "E1=[]\n", "init_param=[]\n", "\n", "H=H1\n", "\n", "for R in range(len(H)): # We try to find the ground state eigenvalue for each radius R\n", " radius1.append(H[R][0])\n", " if len(init_param) == 0:\n", " init_param = [2*np.pi*random.random() for _ in List_Parameters]\n", " else:\n", " for i in range(len(init_param)):\n", " init_param[i] = VQE.get_parameters()[i]._value\n", " \n", " # Finding the ground state eigen value for each H(R)\n", " result = minimize(minimize_loss, init_param, method='Nelder-Mead')\n", " \n", " E1.append(result.get('fun'))\n", " tq.set_description('Finished' )\n", " " ] }, { "cell_type": "markdown", "id": "29c9e0f2", "metadata": {}, "source": [ "#### Simulation 1: computing the theoretical eigenvalues of H\n", "\n", " We use the numpy linalg package." ] }, { "cell_type": "code", "execution_count": 9, "id": "5c5bba77", "metadata": {}, "outputs": [], "source": [ "E1_th = []\n", "for h in H:\n", " l0 = np.linalg.eigvals(h[1])\n", " l0.sort()\n", " E1_th.append(min(l0))" ] }, { "cell_type": "markdown", "id": "dad5309a", "metadata": {}, "source": [ "#### Simulation 1: plotting the results\n", "\n", "The minimum eigenvalues of H are plotted in orange.\n", "\n", "The eigenvalues found with Perceval are the crosses" ] }, { "cell_type": "code", "execution_count": 10, "id": "6134240e", "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "The minimum energy is E_g(0.9)=-5.725241528105799 MJ/mol and is attained for R_min =0.9 pm\n" ] } ], "source": [ "plt.plot(100*np.array(radius1),E1_th,'orange')\n", "plt.plot(100*np.array(radius1),E1,'x')\n", "plt.ylabel('Energy (MJ/mol)')\n", "plt.xlabel('Atomic separation R (pm)')\n", "plt.legend(['Theoretical eigenvalues', 'Eigenvalues computed with Perceval'])\n", "plt.show()\n", "\n", "\n", "plt.plot(100*np.array(radius1),E1_th,'orange')\n", "plt.plot(100*np.array(radius1),E1,'x')\n", "plt.axis([50,250,-5.8,-5.5])\n", "plt.ylabel('Energy (MJ/mol)')\n", "plt.xlabel('Atomic separation R (pm)')\n", "\n", "plt.legend(['Theoretical eigenvalues', 'Eigenvalues computed with Perceval'])\n", "\n", "plt.show()\n", "\n", "min_value=min(E1)\n", "min_index = E1.index(min_value)\n", "print('The minimum energy is E_g('+str(radius1[min_index])+')='+str(E1[min_index])+' MJ/mol and is attained for R_min ='+str(radius1[min_index])+' pm')\n" ] }, { "cell_type": "markdown", "id": "c5eeb76b", "metadata": {}, "source": [ "### Simulation n°2" ] }, { "cell_type": "code", "execution_count": 11, "id": "f872bf8a", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "d0f4876cbfb64e1b92d7a4c5b500240d", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Minimizing...: 0it [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "tq = tqdm(desc='Minimizing...') #New progress bar\n", "radius2=[]\n", "E2=[]\n", "init_param=[]\n", "\n", "H=H2\n", "\n", "for R in range(len(H)): #We try to find the ground state eigenvalue for each radius R\n", " radius2.append(H[R][0])\n", " if len(init_param) == 0: #\n", " init_param = [2*np.pi*random.random() for _ in List_Parameters]\n", " else:\n", " for i in range(len(init_param)):\n", " init_param[i] = VQE.get_parameters()[i]._value\n", " \n", " # Finding the ground state eigen value for each H(R)\n", " result = minimize(minimize_loss, init_param, method='Nelder-Mead')\n", " \n", " E2.append(result.get('fun'))\n", " tq.set_description('Finished')\n" ] }, { "cell_type": "markdown", "id": "b03f9998", "metadata": {}, "source": [ "#### Simulation 2: computing the theoretical eigenvalues of H\n", "\n", " We use the numpy linalg package." ] }, { "cell_type": "code", "execution_count": 12, "id": "44b8aae9", "metadata": {}, "outputs": [], "source": [ "E2_th=[]\n", "for h in H:\n", " l0=np.linalg.eigvals(h[1])\n", " l0.sort()\n", " E2_th.append(min(l0))" ] }, { "cell_type": "markdown", "id": "008925dc", "metadata": {}, "source": [ "#### Simulation 2: plotting the results\n", "\n", "The minimum eigenvalues of H are plotted in orange.\n", "\n", "The eigenvalues found with Perceval are the crosses" ] }, { "cell_type": "code", "execution_count": 13, "id": "ea56c529", "metadata": {}, "outputs": [ { "data": { "image/png": "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1KlT/O53v7MkUUNZovAX413wyJqejAHw9UuYms36KPz5+ihyIxuHMcZUIpYo/NVpDBJtNQpjjPFjicKfREFMU6tRGGOMH0sUxcU0sxqFMcb4sURRXEwsnLAaRU0VHR3tm2o8OTmZRx99FIDbbrutyGyukVI4YV91Vnzm3JIq62tz7bXXcvjwYQ4fPszf//533/aSTnU+ffp03xQvvXr1CjpiPRJmz56Ny+W66P1YoijOahRVQriWr73kkkt8U4uvXbuW++67D4Dnn3++wtekqKnKmijKatGiRTRu3Pi8RFEajz/+OGvXruXRRx8t1bQoFxpBX1lYoiguJtb6KKqAil6+tnBabIAXXniBLl260K9fP26//XbfN7bc3FzGjRtH37596du3L1988QXgmSb71ltvZejQoXTs2JG0tDQA7rvvPv72t7/5jlE4nfaxY8cYMWIEvXr1IjEx0Tda21/xb7sul8s3BUawKcDT0tLo3r07SUlJTJo06bx9nj17lt/85jf06NGDpKQk/vrXvwKeyfdSUlJITEzk1ltv9U1cGBcXx/33309ycjJ9+vRh9erVXH311XTq1Mk3l9PSpUsZPHgw1113HVdccQUzZszwzdvkXwNYuHAh06dPDzjFerCp3Hfs2OEbWf7f//3fAd+3xx9/3Pd6/+pXv2L48OEAfPLJJ0ydOtV3HgcOHOC+++4jKyuL5ORk7rnnHsAz4HD8+PF07dqVqVOncqEByoMHD2bbtm1A4CnQC8/717/+NT179mT58uXMnTuXpKQkevbsyS233AIE/lsqKCggLi6uyGy4nTt3Zt++fbz33nv079+flJQURo4cyb59+0LGWWqqWu1+evfurWW27neqr0Spnj1T9n2YMtm0aVOpyn+xLVdTHvlQ//LBN5ryyIf6xbbci44hKipKe/bs6fuZP3++qqoOGTJEV65cqXv37tUOHTpoXl6enjp1Sq+66ir9xS9+oaqqkydP1mXLlqmq6q5du7Rr166qqvqHP/xBBw4cqCdOnNDc3Fxt0qSJnjp1SlevXq2DBw/2Hbtbt266e/duPX36tObn56uqam5urnbq1EkLCgpUVbVevXqqqrpkyRK97rrrfM/9xS9+oS+99JKeOnVKBw4cqPv371dV1fnz5+tPf/pTVVVt1aqVnjhxQlVVDx06dN65//3vf9dx48bp6dOnVVU1Ly9Pjx8/rm3bttUtW7aoquott9yiTz31lKqqdujQQf/+97+rquovf/lLTUxM1CNHjuj+/fu1efPmvjhjYmI0KytLz5w5oyNHjtTXX3+9yLmoqr7++us6bdo0VVWdNm2ar4yq6vDhw/Xbb79VVdUvv/xShw0bpqqqY8eO1Tlz5qiqanp6epH9FVq+fLmOHz9eVVWvuuoq7du3r546dUofeughnTVrlu88cnNzdceOHZqQkOB77pIlS7Rhw4a6Z88ePXv2rA4YMMD3/vrzj/e1117Tfv366aZNm3TMmDF66tQpVVW98847fbECumDBAlVV3bhxo3bu3Flzc3N9r7lq8L+l1NRUffHFF32vxYgRI1RV9eDBg76/kX/84x86c+ZMVVV96aWXfH+f/gL9rwGZGuQzNaID7kTkGuBpIBp4XlUfLfZ4DDAX6A3kARNVdWdYg4qJBS2AU4egrk0zXpk5OsXi7N+etE+2kTr88nKZFr6w6SmYFStWMGTIEJo0aQJ4pgwvnNJ78eLFRZpMjhw54pt077rrriMmJoaYmBiaN2/Ovn37SElJYf/+/Xz33Xfk5uZy2WWX0a5dO06fPs0DDzzAZ599RlRUFHv37mXfvn0lGhUdagrwpKQkpk6dyo033siNN9543nMXL17MjBkzqFXL87HQpEkT1q1bR3x8vG9+pWnTpvG3v/2NX/7ylwBcf/31ACQmJnLs2DEaNGhAgwYNiImJ8X3z7devHx07dgRg8uTJfP755+etsxFMqKnYv/jiC9/aFbfccgu//e1vz3t+7969WbVqFUeOHCEmJoZevXqRmZnJsmXLfDWNUPr160fbtm0BSE5OZufOnVx11VXnlbvnnnv44x//SLNmzXjhhReCToEOnn6wcePGAZ6azYQJE4iN9fztFv5dBftbmjhxIo888gg//elPmT9/PhMnTgQ8a5xMnDiRnJwcTp06RXx8/AXPrTQilihEJBr4GzAKyAZWisi7qurfOPkz4JCqXi4ik4A/AxPDGpj/6GxLFJWaO+sAGV/tJnX45WR8tZsBnZpGdA2RgoICvvzyy4BTfBefprxwArwJEyawcOFCvv/+e98//bx588jNzWXVqlXUrl2buLg439TahUJNvR1sCvD333+fzz77jPfee4//+Z//YcOGDb6kUFaF5xUVFVXkHKOionznKCJFnlN433978fMrdKGp2Ivvu7jatWsTHx/P7NmzcTgcJCUlsWTJErZt21aiecWCvW/FPf7440WS35IlS4JOgV63bl2io6NDHjfY39LAgQPZtm0bubm5vP32274mt7vvvpuZM2dy/fXXs3TpUh566KELnltpRLKPoh+wTVW3q+opYD5wQ7EyNwBzvLcXAiPkQn8ZF8tGZ1cJvuVrp6Qwc/QVFbZ8bd++ffn00085dOgQZ86cKbIa2+jRo33t+kCJ1pmYOHEi8+fPZ+HChb5vzfn5+TRv3pzatWuzZMkSdu3add7zOnTowKZNmzh58iSHDx/m448/BoJPAV5QUMCePXsYNmwYf/7zn8nPz/fVdgqNGjWKZ5991vdhePDgQa644gp27tzpa3d/+eWXGTJkSCleMU8tbMeOHRQUFLBgwQLfN/IWLVqwefNmCgoKeOutt3zl/adYDzUV+5VXXllkKvdgBg0axBNPPMHgwYMZNGgQs2bNIiUl5bwkE2xq97IINgV6ccOHD+f1118nLy/PVw6C/y2JCDfddBMzZ86kW7duNG3aFCg6XfucOXMob5FMFG2APX73s73bApZR1TNAPtA00M5E5A4RyRSRzNzci/iQr2vzPVUFvuVrvTUI/+VrL8bx48eLXB5beNVToTZt2vDAAw/Qr18/rrzySuLi4mjUyNOBnpaWRmZmJklJSXTv3t3XoRtKQkICR48epU2bNr4moqlTp5KZmUliYiJz586la9eu5z2vXbt23HzzzfTo0YObb77Zt1JdsCnAz549i9PpJDExkZSUFFJTU2ncuHGRfd522220b9/e17H6yiuvULduXV566SUmTJhAYmIiUVFRzJgxo1Svad++fXG5XHTr1o34+HhuuukmAB599FHGjBmDw+EoskJe8SnWg03F/vTTT/O3v/2NxMRE9u7dG/T4gwYNIicnh4EDB9KiRQvq1q0bcH2Qpk2bcuWVV9KjRw9fZ3ZZBZsCvbiEhAQefPBBhgwZQs+ePZk5cyYQ+m9p4sSJZGRk+Gqg4LkQYsKECfTu3dvXjFWugnVehPsHGI+nX6Lw/i1AerEyG4G2fvezgNgL7fuiOrN/2KM6D9Wtz5Z9H6ZMStuZHSlHjx5VVdXTp0/rmDFj9M0334xwRJVX8U53UzmUtjM7kjWKvUA7v/ttvdsClhGRWkAjPJ3a4WOLF5kLeOihh0hOTqZHjx7Ex8cH7Bg2pjqJ5FVPK4HOIhKPJyFMAqYUK/MuMA1YjqcG8ok384VPdF2oVd9GZ5ugnnjiiUiHUGUMHTo04DrYpmqJWKJQ1TMi4gI+wHN57Iuq+rWIPIKnCvQu8ALwsohsAw7iSSbhFxNrNYoIUdULXslijCm7snzXjug4ClVdBCwqtu33frdPABOKPy/sYprZVU8RULduXfLy8mjatKklC2PCQFXJy8sLeAl3KLbCXSAxsXByf6SjqHHatm1LdnY2F3XVmjEmpLp16/oGEZaUJYpA6jaD/K8jHUWNUzg4yhhTudikgIFYH4UxxvhYoggkphmc/RHO/BjpSIwxJuIsUQRiYymMMcbHEkUgvmk8rFPVGGMsUQRSWKM4YTUKY4yxRBFIjNUojDGmkCWKQKyPwhhjfCxRBFKnMUi01SiMMQZLFIFJFMQ0tRqFMcZgiSK4mGY2g6wxxmCJIjgbnW2MMYAliuBsBlljjAEsUQRnNQpjjAEsUQRXtxmczIOCs5GOxBhjIsoSRTAxsYDCqUORjsQYYyLKEkUwNjrbGGMASxTB2ehsY4wBLFEEZzPIGmMMYIkiOKtRGGMMYIkiON9U41ajMMbUbBFJFCLSREQ+EpGt3t+XBSiTLCLLReRrEVkvIhMrNMjoulCrvtUojDE1XqRqFPcBH6tqZ+Bj7/3ifgT+U1UTgGuA/xORxhUXIjY62xhjiFyiuAGY4709B7ixeAFV/VZVt3pvfwfsB5pVVICAjc42xhgilyhaqGqO9/b3QItQhUWkH1AHyApR5g4RyRSRzNzccqoF1LUZZI0xpla4diwii4GWAR560P+OqqqIaIj9tAJeBqapakGwcqr6HPAcQJ8+fYLur1RiYuHwxnLZlTHGVFVhSxSqOjLYYyKyT0RaqWqONxHsD1KuIfA+8KCqfhmmUIOzPgpjjIlY09O7wDTv7WnAO8ULiEgd4C1grqourMDYzomJhbPH4cyPETm8McZUBpFKFI8Co0RkKzDSex8R6SMiz3vL3AwMBqaLyFrvT3KFRukbnW0d2saYmitsTU+hqGoeMCLA9kzgNu/tDCCjgkMryjc6OxfqtY9oKMYYEyk2MjuUwhlkT1iNwhhTc1miCMW/RmGMMTXUBZueRKQ5cCXQGjgObAQyQ12qWm1YH4UxxgRPFCIyDM/UGk2ANXguYa2LZxR1JxFZCPxFVY9UQJyRUbsRSLTVKIwxNVqoGsW1wO2qurv4AyJSCxgDjALeCFNskSdRNo2HMabGC5ooVPWeEI+dAd4OR0CVTkysTeNhjKnRQjU9zQz1RFV9svzDqYRimlmNwhhTo4VqempQYVFUZjGxkG/zPRljaq5QTU8PV2QglVbdZrDfahTGmJrrguMoRKStiLwlIvu9P2+ISNuKCK5SiImFk3lQcDbSkRhjTESUZMDdS3gm8Wvt/XnPu63am/VpFu6D7QCFU4cAcGcdYNanQZfFMMaYaqckiaKZqr6kqme8P7Op6JXmIiSpbSNcS1viPpYIJ3NxZx3A9coakto2inRoxhhTYUqSKPJExCki0d4fJ5AX7sAqA0enWNKvi8G16z6e/CQb1ytrSJ+SgqNTbKRDM8aYClOSRHErnim/vwdygPHAT8MZVGXi6NwaZ9NFpH11Cmf/9pYkjDE1zgXnelLVXcD1FRBLpeTe14iMvGtJTdxHxld1GNCpqSULY0yNUpJJAeOBu4E4//KqWu2ThzvrAK7XviG987M4Lu/CgAHXW/OTMabGKcnCRW8DL+C52qn6zxjrZ312vicpbDsJx3bg6B9L+pQU1mfnW6IwxtQYJUkUJ1Q1LeyRVEIzhnTy3Pg+HvJWAJ4ObksSxpiapCSJ4mkR+QPwIXCycKOqrg5bVJVNvXjYvdAz6C4qOtLRGGNMhSpJokgEbgGGc67pSb33a4b68aBn4Hg21OsQ6WiMMaZClSRRTAA6quqpcAdTadXv6Pl9bLslCmNMjVOScRQbgcblfWARaSIiH4nIVu/vy0KUbSgi2SKSXt5xlEj9eM/vYzsicnhjjImkkiSKxsA3IvKBiLxb+FMOx74P+FhVOwMfe+8H8/+Az8rhmGVzaTvPaneWKIwxNVBJmp7+EKZj3wAM9d6eAywFflu8kIj0BloA/wb6hCmW0KJqe5LFD5YojDE1T6gV7j7A8+H8L1X9JgzHbqGqOd7b3+NJBsVjiAL+AjiBkWGIoeTqxVuNwhhTI4WqUUwDrgEeEpEuwFd4EsdiVf2hJDsXkcVAywAPPeh/R1VVRDRAubuARaqaLSIXOtYdwB0A7du3L0l4pVO/I3y3qPz3a4wxlVyoFe6+B2YDs73f7PsD/wHcKyLHgQ9V9bFQO1fVoLUAEdknIq1UNUdEWgH7AxQbCAwSkbuA+kAdETmmquf1Z6jqc8BzAH369AmUdC5O/Xg48T2cOQ61Lin33RtjTGVVks5sVLVAVZer6u9V9UpgErD3Io/9Lp5aC97f7wQ47lRVba+qccBvgLmBkkSFqOe98umHnRE5vDHGREqoPoq/4hlYF8hJIEtEGqjq0TIe+1HgNRH5GbALz1TmiEgfYIaq3lbG/YaH/yWyjbpFNhZjjKlAofooMi/wvATgTWBUWQ6sqnnAiADbM4HzkoR3Zb3ZZTlWuShMFHblkzGmhgnVRzHnQk8WkZrTu1u3JUTX9YzONsaYGiRU01PIQXWqer2qXlv+IVVSInaJrDGmRgrV9DQQ2AO8iufS2NDXp9YE9eOt6ckYU+OEShQt8fQ/TAamAO8Dr6rq1xURWKVULx5yv4h0FMYYU6GCXh6rqmdV9d+qOg0YAGwDloqIq8Kiq2zqx8PpfDh1KNKRGGNMhQk515OIxADX4alVxAFpwFvhD6uS8p9uvEnvyMZijDEVJFRn9lygB7AIeFhVN1ZYVJWV/1gKSxTGmBoiVI3CCfwA/BeQ6jfXkuCZnqlhmGOrfOrZuhTGmJon1DiKEk3vUaPUaQR1LrMrn4wxNUrQZCAi9S/05JKUqXZsLIUxpoYJVWt4R0T+IiKDRaRe4UYR6SgiP/OuV3FN+EOsZOp3tNHZxpgaJdTlsSPwLFH6c+BrEckXkTwgA88Yi2mqurBiwqxE6sd7ZpDVgkhHYowxFSLk5bGqugjPVU+mUP14KDgFx3Pg0jaRjsYYY8LOOqxLy658MsbUMJYoSsumGzfG1DCWKEqrXgdArEPbGFNjXDBReK98SqiIYKqE6LpwSWtrejLG1BglqVFsBp4Tka9EZIaINAp3UJWeTTdujKlBLpgoVPV5Vb0S+E88EwOuF5FXRGRYuIOrtGzQnTGmBilRH4WIRANdvT8HgHXATBGZH8bYKq/68fBjNpw9FelIjDEm7EKOowAQkaeAsXgG3/2vqq7wPvRnEdkSzuAqrfodAYUfdkHDzpGOxhhjwuqCiQJYD/y3qv4Q4LF+5RxP1eB/iawlCmNMNVeSRLEOuMJvmnGAfGCXquaX5aAi0gRYgKfPYydws6qet2yciLQHngfaAQpcq6o7y3LMcmWD7owxNUhJ+ij+DnwJPAf8A1gOvA5sEZHRZTzufcDHqtoZT5PWfUHKzQUeV9VueGov+8t4vPJ1SWuIqm1XPhljaoSSJIrvgBRV7aOqvYEUYDswCnisjMe9AZjjvT0HuLF4ARHpDtRS1Y8AVPWYqv5YxuOVm1mfZuHecQgu7eCrUbizDjDr06wIR2aMMeFRkkTRRVW/LryjqpuArqp6MUOTW6hqjvf290CLQMcFDovImyKyRkQe9159FZCI3CEimSKSmZubexGhhZbUthGuV9bgPjUYjm3HnXUA1ytrSGprw0uMMdVTSfooNonIM0DhpbATvdtigNPBniQii/FMR17cg/53VFVFRIPENghPDWY3nj6N6cALgY6nqs/haR6jT58+gfZXLhydYkmfkoJrzlGcsVFkrFxD+pQUHJ1iw3VIY4yJqJIkimnAXcAvvfe/AH6DJ0kEHXSnqiODPSYi+0SklarmiEgrAvc9ZANrC2suIvI2MIAgiaIiOTrF4ux6hLT1N5I6pJklCWNMtRay6cnb1LNIVf+iqjd5f55Q1R9VtUBVj5XxuO/iSUB4f78ToMxKoLGINPPeHw5sKuPxypU76wAZW5uR2vxVMlbsxZ11INIhGWNM2IRMFKp6FigIw/xOjwKjRGQrMNJ7HxHpIyLP+x37N8DHIrIBEDxXXUVUYZ9E+sTuzGw5j/Srtnv6LCxZGGOqqZI0PR0DNojIR4Bv0J2qppb1oKqaB4wIsD0TuM3v/kdAUlmPEw7rs/PP9UlsicdR5zPSp0xmfXa+NUEZY6qlkiSKN70/BpgxpNO5O016w8FVOAbFWpIwxlRbF0wUqjpHRC4B2qtqzZzbKZimfWDPQjh5EGKaRDoaY4wJi5IsXDQWWAv823s/WUTeDXNcVUOT3p7fh1ZHNg5jjAmjkgy4ewjP9BmHAVR1LdAxbBFVJZf18vw+uCqycRhjTBiVJFGcDjD5X0E4gqlyYpp4Jgi0RGGMqcZK0pn9tYhMAaJFpDOQCrjDG1YV0qQ35GVGOgpjjAmbktQo7gYSgJPAq8ARzo3SNk16e2aRPXkw0pEYY0xYlOSqpx/xzM/04IXK1kj+Hdotg85aYowxVVZJlkLtgmeEdJx/eVUdHr6wqpDCRHFwlSUKY0y1VJI+iteBWXhWmjsb3nCqIOvQNsZUcyVJFGdU9ZmwR1KVeUdoG2NMdVSSzuz3ROQuEWklIk0Kf8IeWVXSpDcc2w6nzlv22xhjqrySrkcBcI/fNsUG3Z1j/RTGmGqsJFc9xVdEIFVaE78R2pYojDHVTNCmJxG51+/2hGKP/W84g6pyYppCvTjrpzDGVEuh+igm+d2+v9hj14QhlqrNOrSNMdVUqEQhQW4Hum+a9LEObWNMtRQqUWiQ24HuG1+Htk05boypXkJ1ZvcUkSN4ag+XeG/jvV837JFVNUU6tM9b5dUYY6qsoIlCVaMrMpAqz9ehbTPJGmOql5IMuDMlZR3axphqKGKJwjvC+yMR2er9fVmQco+JyNcisllE0kSk8nak2whtY0w1FMkaxX3Ax6raGfjYe78IEXEAVwJJQA+gLzCkIoMsqVmfZuH+IcVzx9uh7c46wKxPsyIYlTHGXLxIJoobgDne23OAGwOUUTwd53WAGKA2sK8igiutpLaNcP1bcB9LhIOrcGcdwPXKGpLaNop0aMYYc1EimShaqGqO9/b3QIviBVR1ObAEyPH+fKCqmysuxJJzdIolfWpvXLsf4MnlZ3C9sob0KSk4OsVGOjRjjLkoJZkUsMxEZDHQMsBDRVbLU1UVkfPGZojI5UA3oK1300ciMkhVlwUoewdwB0D79u0vNvQycXSKxRm/k7StPUkd3s6ShDGmWghrjUJVR6pqjwA/7wD7RKQVgPf3/gC7uAn4UlWPqeox4F/AwCDHek5V+6hqn2bNmoXrlEJyZx0gY08Cqc1fJWP5dtxZByIShzHGlKdINj29y7kpzKcB7wQosxsYIiK1RKQ2no7sStn0VNgnkT4pkZmt5pM+cAOuV9ZYsjDGVHmRTBSPAqNEZCsw0nsfEekjIs97yywEsoANwDpgnaq+F4lgL2R9dr6nT6JrHDS7CseZeaRPSWF9dn6kQzPGmIsiqtVv2qY+ffpoZmYER0hv/gus+Q3csBPqdYhcHMYYU0IiskpV+wR6zEZmh0ObsZ7fe/8Z2TiMMaYcWKIIh4ZdoEEX2FspW8mMMaZULFGES5uxsG8JnD4a6UiMMeaiWKIIlzZjoeAUfP9RpCMxxpiLYokiXJo5oHZja34yxlR5lijCJao2tP4P2Ps+FJyNdDTGGFNmlijCqc1YOJkLeSsiHYkxxpSZJYpwan0NSLQ1PxljqjRLFOFU5zJoNsgShTGmSrNEEW5txkL+Rji2M9KRGGNMmViiCDcbpW2MqeIsUYTZrDVRuAuuLdL8ZEukGmOqEksUYZbUthGurbd5phs/fdSWSDXGVDlhXeHOeJdIHdsQ11u/xvnOJ2RsvtSWSDXGVClWo6gAjl5DcLZYSlpmLZz921uSMCaCZn2add6CYu6sA0x/acVFb7//zfXc/+b6SrHv8mzetkRRAdw7DpORd60tkWpMAKX54C6PD+LoKPjZ7Ezf9sLm4Csvb1pkVcqybP/n+hz+uT6nUuy7PJu3rekpzM4tkdoDx7pJDGjQAdcrtaz5yVQLsz7NIqltoyJ/y4Uf5H/6SZJvmzvrAM99tp07BncsUtaddYBdeT/w3Gfbff8Thf8zdw7t6Pnf8dv+z/U5AIzt2Tpw2UkJODrU82x/fSt3OmJxZawk/cZYHO2icO/8kWc+OcrMAVG4Xv4KZ6KSsQHSR5/A0Wo5CUPB9bIbZ9ejZGxuQPqQvTiariZhYF1cc3/A2XkfGVtbkO7YgqP+YhL6NsQ19xjOjtlkZLXl2f6rgQJcc37AGb+djB0dSe/jxhG9j4SkWFxzjuDssIWMXVeQnrIEBzkk9GiBa04+znZfk7E7gfSe/8JxahcJV7TGNfswzjZryNibwrM93gbANfsIzjarydjbi/SEN3D8uJOEzh1wzR6Hs91GMvYNKvfPF0sUYeZbIrVTLBy5BcfOJ0kffyvrs/MtUZhKJ9AHf2k/5AN+mM9bzZ2DWuOal0n6DbE42pzFvSMf1/unSB95lLFNj+Oa+6P3g7gZ6f3W4Kg9n4SkBrhm5+Nst56MPYk8m7AQCk7hemkSzhafeT4UO6bh2LuahBZdcM2+D2fTRWTkXUt6h0dx7N9AQqtEXK8V256/gaMNp5K2cjKpzV/FsXMe7AQH4Gw4lbS13u3fz4PvvdsbTSVt42RSWyzAcWg+HIrCIdE4L7uZtM3jSW31Fo4T74BE42yWR9qWsaS2WYRDF8GBKBwShbN5AWlbR5Pa7iMcdZbAkSgcMVtxtryEtKzhpHb4FEeDr+FsNI7LduJsu5a0HVeSGr8cR2wOIDjbridtx1WkdvwSR/ODQCMcLY7gPLKJtO39SR1e/s3bthRqRcrfBO8nQOIjkPi7SEdjaojSfOt/b913fPD1voDf7p9ZkkX6uHY4Wp/EvW2/50N+WC6cPoJrWXuccVlk7IgjPfGfcPoYrk0TcDZfTMa+YZ4P5/obcB9LxLWr2Id2/Q0APPn9VNL2Tya15UJmtn0Xal0K0Zfw5J6xpO0eTmq8m5ldVkF0XZ7c2p+0rUmkdv2WmYm7ILouRNXlyQ3tSdvQgtTkw8zs8yNE1YGoOjyZeQlpmbVJ7QczHTG4s6NwvX8CZ/KlZKw9TvpNLXDEN8S9+ySuN7Jx9mlORuZ+0id297wOO47gmr8OZ/8OZHy1+7zXx9m/vW87cN62YGVLu720+y6NUEuhWo2iIjXqDq3+A7amQ/d7PH/cxpSTYLWBkN/6uzfC0epH3Fv34vrncdIHZzO27yFPE0nb9WTsSSC98z9wfOcmoWUnXK8U+5Df6/mQd142nbQt40lttxhH421QuzHO4ztI23ojqd134eh1M9S6FUftBjhXX0Laqsmk9q+DY8gcqN0A9x4lY+EOUoe3J+OrKQxIedwXa8baNd7tdRgw8lcAZHzqt23QLefKbi/cvpsBfVNwdPRu33xue4Nm8TyzdDvpzn44OsUyoEdhMmzEM0u/I31qb8/2K/yS5NLtpE/p5dneqWmx7Sm+7T9/eRUAz97S+4JlS7u9tPsuz+Ynq1FUtO8/hk9GQv8XoNOtkY7GVEHBEkKR2kDHpri37MK1YDPp15yFE/twfdLU0z6+oyPpXWbDqQO4tv/X+d/uo2J4MvdnpO29ltROK5nZYwfExEJMLE9ujCdtTQNS+9dm5rDWUKcJ7r3gmv91mb/5FpYNWItZWjTBFf+wDFU22Pafzc5k5ujO3D6oU5HXL1jzWmm2l7Z/Jpz7Xp+dz4wh587xQkLVKCxRVDRV+FcK6Bm4dgOIRDoiU0ldMCFMSsDR/DDub7bjWlRA+oA1cDwH1+phOGM/ICN3ZNGmnX23kLZvIqlxnzOzRxZc0oYnt/QkbX0zUvvXYubwDnBJS9y7z+B6tewf8qX9ML86oYWvP8P/PMP5QVzaD9GaoNIlChGZADwEdAP6qWrAT3URuQZ4GogGnlfVR0uy/0qdKAC2z4Uvp8HQf0PrqyMdjakEAiWFfyzL4skPt/LCpHgcTfd4aggfNyE96Z/w4x5cW6YXrQ003g71O/Hkd+NJ29aL1KQDzLzqUri0Pe59jXG9sQvngA4hv/UXbivJN/NgH/Lh/uZrwqMyJopuQAHwLPCbQIlCRKKBb4FRQDawEpisqpsutP9KnyjOnoJ346BRDxj+YaSjMRUoZC1hYw7p19bG0fAb3FtzcH2ZxJ0t3uKZnDHnEkLHp3G0PgMNr+DJHcNI29ia1AGXMPOaRIiJxb0976K+9Zfm2719yFcvla4zW1U3A0joZpd+wDZV3e4tOx+4Abhgoqj0ousw6/QjJG1Lw9FrAzROBOwfrzoJ3bGcRfoNzXA0+hb3lp24lnUi/fJZjG25H9db9+Fs6ibj4HWkJ76FI74hR7MKSFs3mdRBLXFc+yWIeDpoP/HruE0UIK9IQijs1Lw6oUWRjk1Hp1jGJLXy3S78nT4lJeBl245OsQE7RYNtN9VPZb7qqQ2wx+9+NtA/QrGUu6SkEbgy6pG+7GUcYx87NzDP+w3QVA0XvNJocgqOlkdwr1+N64No0hPeYmzrb3EtSD1XS+j+Mo64FnDZaJyxjUhbMZnU4ZfjGD3ZkxA+8EsIXfMASpwQgn34+zcLFbIPfhNM2BKFiCwGWgZ46EFVfScMx7sDuAOgffv25b37cufoGk/6wJdwuZNxRmWSsfqQjdau5AIlhcLpIF6Y3sfThPPNLlwLNpLu+Jax3Tfhmn0AZ5P3PQkh7i84GtWC+D4460eTtmYyqcPicVw9BfAkmIyv/S7jvKRWwMseS5MQ7MPflIewJQpVHXmRu9gLtPO739a7LdjxngOeA08fxUUeu0I4hvwU59aHSVvWyPMN0v6hK7Wkto2KtvVvO8AzS7Yys3c+rjmfeUYKf9fP07GcuxEadcPZ6QrStkwmdWB9HGOWQ3QdT0L4t18t4fLmwPm1hMLLOC0hmEirzE1PK4HOIhKPJ0FMAqZENqTy5d7XgIxDN3kmC3RPZECnpvbPXgkEa05av+ewZ8r4uW6c7TaQsbMT6e3/F8ePGzja9Gek7bmJ1MQcHCOehCZ9ce85TcZqv4TQ4whQ8majF6b3YX12fpHYLCGYSIjI7LEicpOIZAMDgfdF5APv9tYisghAVc8ALuADYDPwmqp+HYl4w8HXJ+EcyMyELNLb/xnXvFU2s2wFCjZr6a68HzyzcW7LhUNrcS95FtecpSRtn4Zj0wCcjV4jbVsyzo67cAy7C3fCSjKO3Ezq8MvJ2N4B9w/JuPec9iWEmaOvIH1KCq5X1vDeuu8CNht1aFovYC3BLmwwlYENuIuQIt9a87+BfyXjjrmV9ZfNZMbQyyMdXo3gfwHBucnrMkkf+j3krcK1ynGuf6HrXBxd2uI+PRLXJ41xDogn46vdpR5EZle1mcqq0o2jCLeqkCjOs+lxWHsvOF6FuEmRjqZaCdqUlJ1PUotauOZvwBm3lYxtrUlv/z+ekcyXtuXJQ/9F2pZupA5qzszr+gZMLMGmg7CEYKqaUInCFi6qLLrOhKb9YZULju+LdDTVSmEntDvrAGgB7jVf4Jr7OUnf/x7Hmk44G84n7ZsuODtswTHkdrhuM+4ea8jI7ulpTlp92PfhH6gf4WxB0eNZk5GpbipzZ3bNEhXNLJ4i6eDdODLvgqsW+gZW2bfTkgtUe+D0Ea5udxTXnE89cyDtG+q5MqlRHdxR/0PGt4mkDosnY0UdBtRKgQPgevX8DudAly9b57KpCaxGUYkkXd4ZV/bvcG/eCrtfC8uShtVF6I7o1bjXuGHD/8M9fxKul79k7KmHcMZ+QNp3N+DsGYPjPxfj7vxvXF/1It3Zj5lXd7tgh3PxK5CMqSmsj6KScW/dh2vupzhjPyTj8DjSnX3tG2sA5/UXbNmD69V1pPf5Ag58iWvr7Tib/ouMQ2NJH7wHmvbH9f6PRRaeWZ+dH7TvwmpwpqapdHM9meAcnVvg7N+BtM9vIrXVmzguawnU3EQRqiM6/SdtPOsbt17lWYO4w6M4ju+Cy6/GeWlt0lZPInX45dBxuDep9LKmJGPKwJqeKhl31gEy1hwh9apYMnJH4H7bBQdXRTqsiCnaEa241yz3dETvvRfHugScDV8jbXs/nF0O4rj+/+Anubhb/Z2MLY09HdFf7bamJGMuktUoKpHizSkD4urjmn83V784j7EjTuBIubJI2erURBK05rA7j/TRpz1TZDRbTMb3V5He4c84mjXAHf1XMrZ2InV4R8+SmD8kwQ9HAo58HtuzdZHjWc3BmJKzGkUlct7llz0SSJ+cCFExuN7YjXv1MoAq38kdqCO6cHI9d9YBOJ6D+7O5ntHQWbfgyLoOZ5P3SNt7Hc6kWjj+8yPcHd/G9cUVpDv7lmjks9UejCk768yuCn7Yg/vN23FtmYazdywZG6nSM82e1xG9dT+uVzK5s+tOntkYi/Oydz2jobu8hKN7F9wFY3B9EF1kdTbriDamfFlndlVXrx2Occ/jfPEx0r66mtRuWTjaV/6lOUJ2RE/ohOvl5TjbbyZjezvS2/8JR8HXHG1zL2k7J5PqaIhj7Ie+FdvSp9qYBmMixZqeqgh3Tl0yDl5HatdtZHwbi3vejcx6523c284fSzDr06wKje2Ck+tlHYCC07gzl3g6or+7H8fqy3E2XEDatwk4O+7GMfp+3CnbyMgd7umEXncC9/a8gKOhrSnJmIplNYoqwNdUM7U3jk5XM2D157jemsGdsbNxZZ4hfWI3HD0SIrZK3nnrNGQdwDVvNeljGzD2sixcs/Nxxv6LjP0jSI97HEfTS3HLE2Rs7UrqsI5krKhDgz0deWbpNhsNbUwlZImiCjjvW3Wvq0hvsI/1a38kvf7juOb/EuflH5GxqwtX92h73vPLq+0+ZFPSpARcGStwdt5Pxub6pMc/iWPzcgCcLe4mLfsnpPY5g+P6Jbj3nPFOse5NCpfHlmqRHmNMxbKmpypgxpBO53+r7tyCGROm45i0EGfH3aR90xlng1cY++O9uOZ+jnv9GuBcbWRX3g8Bm4emv7SixNt9VyZt3Q9HvsX9+au45iwj6bv7cKyKw9ngFdLWN8PZYhmOHongmIe752YyDo31NCdtvhT3njM2uZ4xVYxd9VTFFSYCZ98WZHy5i/Qeb8Gh1bh23ouzzSoyvr+S9KH7oF4HXB9EkT6lN47Lm/meF2w9hTsHx/HMp9tJv74xjiZ7cG/ZjWtZB+5s+zHP7L4KZ5NFniuTOj6NI64e7jOjcS3vgbNfazJW5fmav85rkgrSnGSMiSy76qmaOm+AXufWuF6pS/q4+3DW/5K0dcNJbf0Oju//AUB6y0Rcs+/H2eorMr53kN7tNRw/7iGhSydcsw/jbL2SjL29SI//Pxx7vyKhZSKu1+/D2dSbFK54Fkf72hytm0DaJu+VSWPcuHcc8sRxizeOK84t3hOsI9oShTFVh9UoqrBgfQbvrfuOD77eh7O/Z63m9AmdcDT5Do5s5skvTpK2OZ7Uy1cz8/IVoKeh4AxPbutP2g4HqV02MrNnDsTEQkwsT65rSVpmbVKHdmDmNT3O1WAK921jGoypFqxGUU0F+xD+4Ot9Aa8eQrqSsXsNqcPbe6a8GHanr0kow+23fci5pqKMzYXbd9Og3iVFmqnsyiRjagZLFNVMsHEHhbWM4h/yxfsoQm23K5OMqZms6amGCNZM9dxn27ljcMcSb7fmJGOqp1BNT5YojDHGhEwUERlHISITRORrESkQkcAZTKSdiCwRkU3esv9V0XEaY4yJ3IC7jcBPgM9ClDkD/FpVuwMDgF+ISPeKCM4YY8w5EenMVtXNACISqkwOkOO9fVRENgNtgE0VEaMxxhiPKjGFh4jEASnAVyHK3CEimSKSmZubW2GxGWNMdRe2GoWILAZaBnjoQVV9pxT7qQ+8AfxSVY8EK6eqzwHPgaczu5ThGmOMCSJsiUJVR17sPkSkNp4kMU9V3yzp81atWnVARHZd7PEjLBY4cMFSVV9NOM+acI5QM86zOp9jh2APVNoBd+LpwHgB2KyqT5bmuaraLDxRVRwRyQx2qVp1UhPOsyacI9SM86wJ5xhIpC6PvUlEsoGBwPsi8oF3e2sRWeQtdiVwCzBcRNZ6f66NRLzGGFOTReqqp7eAtwJs/w641nv7cyD4ZVHGGGMqRJW46qmGei7SAVSQmnCeNeEcoWacZ004x/NUyyk8jDHGlB+rURhjjAnJEkWEicg1IrJFRLaJyH0BHp8uIrl+Hfq3RSLOiyEiL4rIfhHZGORxEZE072uwXkR6VXSMF6sE5zhURPL93sffV3SMF6sk869Vk/eyJOdZ5d/PUlFV+4nQDxANZAEdgTrAOqB7sTLTgfRIx3qR5zkY6AVsDPL4tcC/8Fy8MAD4KtIxh+EchwL/jHScF3mOrYBe3tsNgG8D/L1Wh/eyJOdZ5d/P0vxYjSKy+gHbVHW7qp4C5gM3RDimcqeqnwEHQxS5AZirHl8CjUWkVcVEVz5KcI5VnqrmqOpq7+2jQOH8a/6qw3tZkvOsUSxRRFYbYI/f/WwC/0GO81bjF4pIu4oJrUKV9HWo6gaKyDoR+ZeIJEQ6mIsRYv61avVeXmCeuWrzfl6IJYrK7z0gTlWTgI+AORGOx5TNaqCDqvYE/gq8Hdlwyq6k869VdRc4z2rzfpaEJYrI2gv41xDaerf5qGqeqp703n0e6F1BsVWkC74OVZ2qHlHVY97bi4DaIlLlFhovwfxr1eK9vNB5Vpf3s6QsUUTWSqCziMSLSB1gEvCuf4Fi7bvX42kvrW7eBf7Te8XMACBfPeuRVBsi0tI7fxki0g/P/15eZKMqnRLOv1bl38uSnGd1eD9Lo9JOClgTqOoZEXEBH+C5AupFVf1aRB4BMlX1XSBVRK7Hs+LfQTxXQVUpIvIqnqtEYr1zfP0BqA2gqrOARXiultkG/Aj8NDKRll0JznE8cKeInAGOA5PUe/lMFVI4/9oGEVnr3fYA0B6qz3tJyc6zOryfJWYjs40xxoRkTU/GGGNCskRhjDEmJEsUxhhjQrJEYYwxJiRLFMYYY0KyRGGMKRMR+b13aplrIh2LCS9LFMaYshoC/Afws0gHYsLLEoWpNETkRhFREenqty1ZRK4tx2M8LyLdy2t/FaH4ayAi1wdau6SM+z7rXU9ho4i8JyKNg5S7REQ+FZFov8178Ez690+/cs1E5N/lEZupPCxRmMpkMvC593ehZDwjfcuFqt6mqpvKa3/lRURCzZKQjN9roKrvquqj5XTo46qarKo98Iz8/0WQcrcCb6rqWb9t9YFlQCO/2HKBHBG5spziM5WAJQpTKXhn6rwKTzPGJO+2OsAjwETvt96JItJERN72to1/KSJJ3rIPicgcEVkmIrtE5Cci8piIbBCRf3sneUNElopIH+/ta0RktXeq6I8DxJQgIiu8x14vIp29251+258t/JYtIsdE5CnxrIr2sYg0826/XURWeo/zhohc6t0+W0RmichXwGMi0k9ElovIGhFxi8gVQV6D6SKS7t1HnIh84o3vYxFp77fvNO9+tovI+BK8DcsJPiX4VOAdv9emEZ6Fmu7yPubv7QDbTBVmicJUFjcA/1bVb4E8EentXczp98AC77feBcDDwBrvtOsPAHP99tEJGI5n8sQMYImqJuKZi+c6/4N5P8T/AYzzThU9IUBMM4CnVTUZ6ANki0g3YCJwpXf7Wc59KNbDM0dXAvApnvmewPNNvK/3OJsp2qbfFnCo6kzgG2CQqqZ4z/t/g7wG/v4KzPG+HvOANL/HWuFJvmOAkDUQb7IbQbFJKb2P1QE6qupOv80TgHdUdSMQU5hEvTKBQaGOZ6oWSxSmspiMZ4U/vL8nByl3FfAygKp+AjQVkYbex/6lqqeBDXgmWSxsK98AxBXbzwDgM1Xd4d1XoNXplgMPiMhv8aw9cBzPh2lvYKV3wrgReJayBSgACj/IM7yxAvTw1nQ24Ekq/ovcvO7XnNMIeF08624/VaxcMAOBV7y3X/Y7JsDbqlrgbWprEeT5l3jP43tvmY8ClIkFDhfbNhV41Xv7VYrWIPYDrUsQu6kibPZYE3Ei0gRPTSBRRBTPh7yKyD2l3NVJAFUtEJHTfrN5FlCGv3VVfcXbLHQdsEhEfo5nLeg5qnp/SXbh/T0buFFV14nIdDyzzBb6we/2/8NTC7pJPCurLS1tzMWc9LstQcocV9Vkb3PYB3j6KNKKlwHq+nYk0hZwAK+JZ6btWnjO4yFvkbre55hqwmoUpjIYD7ysqh1UNU5V2wE78DRfHMWzwH2hZXi/vYrIUOBAGVdZ+xIYLCLx3n01KV5ARDoC21U1DU/7fBLwMTBeRJoXPk9EOnifEuU9F4ApeDrm8caf4+0nCdV234hzi/xM99te/DXw58bbp+Pd97IQ+w9KVX8EUoFfF+9YV9VDQLSIFCaLqcAT3vcqTlXbAvvEsy4DQBdgY1niMJWTJQpTGUwG3iq27Q3v9iVA98KOXDzfWnuLyHo87e7TynJA79U5dwBvisg6zjUZ+bsZ2OhtmukBzPU24/w38KE3ho/w9AWA51t1P2/T0XA8ndAAv8Oz5vIXePohgnkM+JOIrKFoDaj4a+DvbuCn3lhuAf7rgicfhKquAdYTuNnvQ841a03l/PfrLcDpvT0MeL+scZjKx9ajMKaciMgxVa0f6TjCQUR6Ab9S1VtKUPYz4AZvTcRUA1ajMMZckKquBpZI0QF35/FeTfakJYnqxWoUxhhjQrIahTHGmJAsURhjjAnJEoUxxpiQLFEYY4wJyRKFMcaYkCxRGGOMCen/AxeWced6FezSAAAAAElFTkSuQmCC\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZIAAAEMCAYAAADu7jDJAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAAsTAAALEwEAmpwYAABCOUlEQVR4nO3deXiU5dX48e9JWGUnYZM17GtI2BlkF/RVRC0gArGoVUt1TFta97et+rY/bbX6No2WuoJGBEUQfKWCIJsMIPsuQlgDCEnYlTU5vz+eyZiELEOSyWQ5n+uaK7M88zxnksycue/7uc8tqooxxhhTUCHBDsAYY0zpZonEGGNMoVgiMcYYUyiWSIwxxhSKJRJjjDGFYonEGGNMoQQlkYhIXRH5UkR2e3/WyWW7ZiKyUER2isgOEWlRzKEaY4zJR7BaJE8Ci1W1DbDYezsn7wEvqWoHoBdwvJjiM8YY4ycJxoREEdkFDFLVoyLSCFiqqu2ybdMReENVbyj2AI0xxvitQpCO20BVj3qvfw80yGGbtsApEZkNRACLgCdVNS2nHYrIQ8BDANWqVevevn37oo/aGGPKqPXr16eoar2CPDdgiUREFgENc3jomcw3VFVFJKdmUQWgPxANHARmAvcCb+d0PFV9A3gDoEePHrpu3boCx26MMeWNiBwo6HMDlkhU9cbcHhORYyLSKFPXVk5jH0nAJlXd633Op0AfckkkWQ9+BQBPYgpbkk4zaWCra38Bxhhj/BKswfZ5wETv9YnA3By2WQvUFpGMptYQYIdfe0+/jCcxBff0jUQ2qVXYWI0xxuQhWInkRWCYiOwGbvTeRkR6iMhbAN6xkN8Di0VkKyDAm/7s/NjZy7inbyR+fDSuVuEBeQHGGGMcQTlrK9AqN2qjL0z7PyYPb5f/xqbEuHz5MklJSVy4cCHYoRhTZlWpUoUmTZpQsWLFLPeLyHpV7VGQfQbrrK2Aql/lEglrDtKnVZi1SEqRpKQkatSoQYsWLRCRYIdjTJmjqqSmppKUlERERESR7bdMlkhpUPkM8eOjcU/fiCcxJdjhGD9duHCBsLAwSyLGBIiIEBYWVuSt/jKZSEi7iKtVOPHjo9mSdDrY0ZhrYEnEmMAKxHusTHZtkX4RVHG1CreuLWOMCbCy2SLRdDh/JNhRmFImNTWVqKgooqKiaNiwIY0bNyYqKoratWvTsWPHYo3l008/ZceOn852/+Mf/8iiRYuueT/79++nc+fO1/Qcl8t1zccpakuXLmXEiBHBDsP4qWwmEoCz3wU7AlPKhIWFsWnTJjZt2sSkSZP47W9/67sdElL0b5UrV67k+lj2RPL8889z4425zvEtUh6Pp1iOY8qOMpxIdgc7AlOGpKWl8eCDD9KpUyeGDx/O+fPnAUhMTOTmm2+me/fu9O/fn2+//RZwWgJDhgwhMjKSoUOHcvDgQQDuvfdeJk2aRO/evXn88cdzfL7H42HevHk89thjREVFkZiYyL333susWbMAWLt2LS6Xi65du9KrVy/Onj3L/v376d+/P926daNbt25+JYOXXnqJnj17EhkZyZ/+9Cff/dWrVwcgPT2dhx9+mPbt2zNs2DBuueUWXwzr169n4MCBdO/enZtuuomjR53SeYMGDeKJJ56gV69etG3blhUrVgDQp08ftm/f7jvGoEGDWLduHd988w19+/YlOjoal8vFrl27rorz2Wef5eWXX/bd7ty5M/v37wcgISGBXr16ERUVxS9/+UvS0tJIS0vj3nvvpXPnznTp0oVXX33Vj7+wKYyyOUaCWCIp7db/Bk5uKtp91omC7v9boKfu3r2bDz/8kDfffJO77rqLTz75hJiYGB566CGmTJlCmzZtWLNmDQ8//DBfffUVjz76KBMnTmTixIm88847xMbG8umnnwLOac4ej4fQ0FCGDh2a4/NHjhzJiBEjGD16dJY4Ll26xNixY5k5cyY9e/bkzJkzVK1alfr16/Pll19SpUoVdu/ezbhx48ir3tzChQvZvXs333zzDarKyJEjWb58OQMGDPBtM3v2bPbv38+OHTs4fvw4HTp04P777+fy5cs8+uijzJ07l3r16jFz5kyeeeYZ3nnnHcBpaX3zzTfMnz+f5557jkWLFjF27Fg++ugjnnvuOY4ePcrRo0fp0aMHZ86cYcWKFVSoUIFFixbx9NNP88knn/j1N9m5cyczZ85k5cqVVKxYkYcffpgPPviATp06cfjwYbZt2wbAqVOnruEvbQqibCaS0MqWSEyRioiIICoqCoDu3buzf/9+zp07h8fjYcyYMb7tLl68CMCqVauYPXs2APfccw+PP/64b5sxY8YQGhqa5/Nzs2vXLho1akTPnj0BqFmzJgA//PADbrebTZs2ERoaynff5d21u3DhQhYu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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "The minimum energy is E_g(0.75)=-1.14559912408428 MJ/mol and is attained for R_min =0.6 Å\n" ] } ], "source": [ "plt.plot(np.array(radius2),E2_th,'orange')\n", "plt.plot(np.array(radius2),E2,'x')\n", "plt.ylabel('Energy (MJ/mol)')\n", "plt.xlabel('Atomic separation R (Å)')\n", "plt.legend(['Theoretical eigenvalues', 'Eigenvalues computed with Perceval'])\n", "plt.show()\n", "\n", "\n", "plt.plot(np.array(radius2),E2_th,'orange')\n", "plt.plot(np.array(radius2),E2,'x')\n", "plt.axis([0.3,2,-1.2,-0.6])\n", "plt.ylabel('Energy (MJ/mol)')\n", "plt.xlabel('Atomic separation R (Å)')\n", "plt.legend(['Theoretical eigenvalues', 'Eigenvalues computed with Perceval'])\n", "plt.show()\n", "\n", "min_value=min(E2)\n", "min_index = E2.index(min_value)\n", "print('The minimum energy is E_g('+str(radius2[min_index])+')='+str(E2[min_index])+' MJ/mol and is attained for R_min ='+str(radius1[min_index])+' Å')\n" ] }, { "cell_type": "markdown", "id": "5cc56c1e", "metadata": {}, "source": [ "### Simulation n°3" ] }, { "cell_type": "code", "execution_count": 14, "id": "75e1e21b", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "a108c1e94309416fb558969a5447e8e4", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Minimizing...: 0it [00:00, ?it/s]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "tq = tqdm(desc='Minimizing...') #New progress bar\n", "radius3=[]\n", "E3=[]\n", "init_param=[]\n", "\n", "H=H3\n", "\n", "for R in range(len(H)): #We try to find the ground state eigenvalue for each radius R\n", " radius3.append(H[R][0])\n", " if len(init_param) == 0: #\n", " init_param = [2*np.pi*random.random() for _ in List_Parameters]\n", " else:\n", " for i in range(len(init_param)):\n", " init_param[i] = VQE.get_parameters()[i]._value\n", " \n", " # Finding the ground state eigen value for each H(R)\n", " result=minimize(minimize_loss, init_param, method='Nelder-Mead')\n", " \n", " E3.append(result.get('fun'))\n", " tq.set_description('Finished' )\n", " " ] }, { "cell_type": "markdown", "id": "9a5bbc69", "metadata": {}, "source": [ "#### Simulation 3: computing the theoretical eigenvalues of H\n", "\n", " We use the numpy linalg package." ] }, { "cell_type": "code", "execution_count": 15, "id": "d50017fd", "metadata": {}, "outputs": [], "source": [ "E3_th=[]\n", "for h in H:\n", " l0=np.linalg.eigvals(h[1])\n", " l0.sort()\n", " E3_th.append(min(l0))" ] }, { "cell_type": "markdown", "id": "cbbe1d0e", "metadata": {}, "source": [ "#### Simulation 3: plotting the results\n", "\n", "The minimum eigenvalue of H is plotted in orange. \n", "\n", "The eigenvalues found with Perceval are the crosses" ] }, { "cell_type": "code", "execution_count": 16, "id": "6e91159d", "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "The minimum energy is E_g(0.75)=-1.137117274609139 Hartree and is attained for R_min =0.75 Å\n" ] } ], "source": [ "plt.plot(np.array(radius3),E3_th,'orange')\n", "plt.plot(np.array(radius3),E3,'x')\n", "plt.ylabel('Energy (Hartree)')\n", "plt.xlabel('Atomic separation R (Å)')\n", "plt.legend(['Theoretical eigenvalues', 'Eigenvalues computed with Perceval'])\n", "plt.show()\n", "\n", "\n", "plt.plot(np.array(radius3),E3_th,'orange')\n", "plt.plot(np.array(radius3),E3,'x')\n", "plt.axis([0,2.5,-1.2,-0.6])\n", "plt.ylabel('Energy (Hartree)')\n", "plt.xlabel('Atomic separation R (Å)')\n", "plt.legend(['Theoretical eigenvalues', 'Eigenvalues computed with Perceval'])\n", "plt.show()\n", "\n", "min_value=min(E3)\n", "min_index = E3.index(min_value)\n", "print('The minimum energy is E_g('+str(radius3[min_index])+')='+str(E3[min_index])+' Hartree and is attained for R_min ='+str(radius3[min_index])+' Å')" ] }, { "cell_type": "markdown", "id": "c242b755", "metadata": {}, "source": [ "## References\n", "\n", "> [1] A. Peruzzo, J. McClean, P. Shadbolt, M.-H. Yung, X.-Q. Zhou, P. J. Love,A. Aspuru-Guzik, and J. L. O’Brien, “A variational eigenvalue solver on a photonicquantum processor”, [Nature Communications](https://www.nature.com/articles/ncomms5213) 5, 4213 (2014).\n", "\n", "> [2] P.J.J. O’Malley et al., \"Scalable Quantum Simulation of Molecular Energies\", [Phys. Rev. X](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.73.58) 6, 031007 (2016)\n", "\n", "> [3] J.I. Colless, et al., \"Computation of Molecular Spectra on a Quantum Processor with an Error-Resilient Algorithm\", [Phys. Rev. X](https://journals.aps.org/prx/abstract/10.1103/PhysRevX.8.011021) 8, 011021 (2018)\n", "\n", "> [4] J. A. Nelder and R. Mead, “A simplex method for function minimization”, [The computer journal](https://people.duke.edu/~hpgavin/cee201/Nelder+Mead-ComputerJournal-1965.pdf) 7, 308–313 (1965)." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/Walkthrough-cnot.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "id": "29990c15", "metadata": {}, "source": [ "# CHSH experiment" ] }, { "cell_type": "markdown", "id": "c1e5e2ac", "metadata": {}, "source": [ "In this notebook, we aim to reproduce the $\\mathsf{CNOT}$ Gate to \n", "evaluate its performance while demonstrating key features of Perceval. We use as basis the implementation from [[1]](#Reference)." ] }, { "cell_type": "code", "execution_count": 1, "id": "bb751c13", "metadata": {}, "outputs": [], "source": [ "import math\n", "\n", "import perceval as pcvl" ] }, { "cell_type": "markdown", "id": "cb91de5c", "metadata": {}, "source": [ "### Ralph CNOT Gate" ] }, { "cell_type": "markdown", "id": "d1d6cd32", "metadata": {}, "source": [ "We start by building the circuit as defined by the paper above - it is a circuit on six modes (labelled from 0 to 5 from top to bottom) consisting of five beam splitters. Modes 0 and 1 contain the control system while modes 2 and 3 encode the target system. Modes 4 and 5 are unoccupied ancillary modes (they are not displayed, but they are required)." ] }, { "cell_type": "code", "execution_count": 2, "id": "edafa605", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = pcvl.catalog['postprocessed cnot'].build_processor()\n", "\n", "pcvl.pdisplay(p, recursive=True)" ] }, { "cell_type": "markdown", "id": "0290b85a", "metadata": { "collapsed": false }, "source": [ "Simulations will run on this circuit, using four different input states corresponding to the two-qubit computational basis states. The `Analyzer` is used to compute the gate performance." ] }, { "cell_type": "code", "execution_count": 3, "id": "87a00b53", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
00 01 10 11
00 1 0 0 0
01 0 1 0 0
10 0 0 0 1
11 0 0 1 0
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "performance = 1/9, fidelity = 100.0%\n" ] } ], "source": [ "states = {\n", " pcvl.BasicState([1, 0, 1, 0]): \"00\",\n", " pcvl.BasicState([1, 0, 0, 1]): \"01\",\n", " pcvl.BasicState([0, 1, 1, 0]): \"10\",\n", " pcvl.BasicState([0, 1, 0, 1]): \"11\"\n", "}\n", "\n", "ca = pcvl.algorithm.Analyzer(p, states)\n", "ca.compute(expected={\"00\": \"00\", \"01\": \"01\", \"10\": \"11\", \"11\": \"10\"})\n", "pcvl.pdisplay(ca)\n", "print(f\"performance = {pcvl.simple_float(ca.performance)[1]}, fidelity = {ca.fidelity*100}%\")" ] }, { "cell_type": "markdown", "id": "0cd5fcf4", "metadata": {}, "source": [ "Beyond the actual logic function, what is interesting with this gate us that it produces entangled states that we will be trying to check with CHSH experiment when the source is not perfect." ] }, { "cell_type": "markdown", "id": "c0fb4c12", "metadata": {}, "source": [ "### Checking for entanglement with CHSH experiment\n", "![https://en.wikipedia.org/wiki/File:Two_channel_bell_test.svg](https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Two_channel_bell_test.svg/1340px-Two_channel_bell_test.svg.png)\n", "*https://en.wikipedia.org/wiki/File:Two_channel_bell_test.svg*" ] }, { "cell_type": "markdown", "id": "4e523cfc", "metadata": {}, "source": [ "To reproduce this Bell test protocol, we define a new circuit which uses the $\\mathsf{CNOT}$ gate implemented above as a sub-circuit. The parameters $a$ and $b$ describe the measurement bases used by players $A$ and $B$ in the runs of the Bell-test. We define a noise model that represent a photon source with a brightness of 40% and a purity of 99% that will be used by the `Processor`." ] }, { "cell_type": "code", "execution_count": 4, "id": "6b889867", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "POSTPROCESSED CNOT\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=1.910633\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=a\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "\n", "Θ=b\n", "\n", "H\n", "\n", "\n", "\n", "\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald2]\n", "0\n", "\n", "[herald3]\n", "0\n", "\n", "[ctrl]\n", "\n", "[data]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "0\n", "1\n", "2\n", "3\n", "0\n", "1\n", "2\n", "3\n", "" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "noise = pcvl.NoiseModel(brightness=0.4, g2=0.01)\n", "\n", "QPU = pcvl.Processor(\"SLOS\", 4, noise=noise)\n", "QPU.add(0, pcvl.BS.H())\n", "QPU.add(0, p)\n", "\n", "a = pcvl.Parameter(\"a\")\n", "b = pcvl.Parameter(\"b\")\n", "\n", "QPU.add(0, pcvl.BS.H(theta=a))\n", "QPU.add(2, pcvl.BS.H(theta=b))\n", "\n", "pcvl.pdisplay(QPU, recursive=True)" ] }, { "cell_type": "markdown", "id": "1c96eaec", "metadata": {}, "source": [ "We start by setting the values of the two parameters to 0, meaning that the beam splitters after the $\\mathsf{CNOT}$ effectively act as the identity. " ] }, { "cell_type": "code", "execution_count": 5, "id": "1e935739", "metadata": {}, "outputs": [], "source": [ "a.set_value(0)\n", "b.set_value(0)" ] }, { "cell_type": "markdown", "id": "3451e7d1", "metadata": {}, "source": [ "We now state that our photons will be inputted on ports 0 and 2 (using the 0-index convention, and not counting heralded modes)." ] }, { "cell_type": "code", "execution_count": 6, "id": "1b626655", "metadata": {}, "outputs": [], "source": [ "QPU.min_detected_photons_filter(2)\n", "QPU.with_input(pcvl.BasicState([1, 0, 1, 0]))" ] }, { "cell_type": "markdown", "id": "fcdef3f6", "metadata": {}, "source": [ "We now detail the different state vectors that are the probabilistic inputs to the circuit. The most frequent input is the empty state, followed by two states with only a single photon on either of the input ports, then by the expected nominal input $|1,0,1,0,0,0\\rangle$. Here, the heralded modes are shown because if there were a photon on them, they would also have the imperfect source. They are represented at the end of the state (the two last modes) as they have been added after the declaration of the processor." ] }, { "cell_type": "code", "execution_count": 7, "id": "e8af01a9", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,0,0,0,0,0> 9/25
|{0},0,0,0,0,0> 0.2395
|0,0,{0},0,0,0> 0.2395
|{0},0,{0},0,0,0> 0.1594
|0,0,{0}{4},0,0,0> 4.8193e-4
|{0}{2},0,0,0,0,0> 4.8193e-4
|{0},0,{0}{4},0,0,0> 3.2064e-4
|{0}{2},0,{0},0,0,0> 3.2064e-4
|{0}{2},0,{0}{4},0,0,0>0
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "pcvl.pdisplay(QPU.source_distribution, precision=1e-4)" ] }, { "cell_type": "markdown", "id": "73af9f32", "metadata": {}, "source": [ "We can then check the output state distribution corresponding to this input distribution. By default, since our input state had 2 photons, only states having at least 2 detected photons are kept. This can be changed using `min_detected_photons_filter`." ] }, { "cell_type": "code", "execution_count": 8, "id": "cbee9802", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
state probability
|0,1,0,1> 0.495518
|1,0,1,0> 0.495518
|0,1,1,0> 0.00747
|1,0,0,1> 0.001494
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "output_distribution=QPU.probs()[\"results\"]\n", "pcvl.pdisplay(output_distribution, max_v=10)" ] }, { "cell_type": "markdown", "id": "3cda6b52", "metadata": {}, "source": [ "Let us run now the experiment with increasing value of g2 in the range $[0, 0.2]$ with a brightness of $0.15$ and check the CHSH inequality." ] }, { "cell_type": "code", "execution_count": 9, "id": "3f29137a", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "30475b1501b14a19ad6caef1e7cb322a", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/40 [00:00" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "plt.title(\"CHSH value with purity\")\n", "plt.xlabel(\"g2 (%)\")\n", "plt.ylabel(\"Bell inequality\")\n", "plt.axhline(y=2, linewidth=2, color=\"red\", label='horizontal-line')\n", "plt.plot(x, y, color =\"green\")\n", "plt.grid(color='b', dashes=(3, 2, 1, 2))\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "1329a625", "metadata": {}, "source": [ "Beyond 13% of g2, we are crossing the value $2$, i.e. not violating anymore the $|CHSH|\\le 2$ inequality!" ] }, { "cell_type": "markdown", "id": "e55db30c", "metadata": {}, "source": [ "## Reference\n", "\n", "> [1] T. C. Ralph, N. K. Langford, T. B. Bell, and A. G. White. Linear optical controlled-NOT gate in the coincidence basis. [Physical Review A](https://link.aps.org/doi/10.1103/PhysRevA.65.062324), 65(6):062324, June 2002. Publisher: American Physical Society." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 } ================================================ FILE: docs/source/notebooks/quantum_kernel_methods.ipynb ================================================ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Quantum kernel methods using linear optics\n", "\n", "This notebook follows closely the theory and implementation of Yin et al, \"Experimental quantum-enhanced kernels on a photonic processor,\" *arXiv*, 2024. https://arxiv.org/abs/2407.20364 [1].\n", "\n", "Kernel methods are a class of algorithm in machine learning used for classification, clustering and regression. These methods exploit feature maps which project a dataset of interest to a higher-dimensional space. For classification tasks, the data is then linearly separable by a hyperplane in the new space. \n", "\n", "![Feature map transformation](../_static/img/kernel_transformation.png)\n", "\n", "Behind kernel methods is the kernel trick where the mapping is not explicitly calculated. Instead, the inner product between feature vectors is evaluated for each pair of points.\n", "$$0 \\leq \\kappa(x_{i}, x_{j}) \\leq 1$$\n", "\n", "For pairwise datasets, $x$ and $x'$, these inner products can be contained in the kernel matrix:\n", "$$ K_{i, j} = \\kappa(x_{i}, x_{j}') = \\kappa(x_{j}', x_{i}) $$\n", "\n", "In quantum kernel methods, we often consider the fidelity-based quantum kernel, which for an input feature map $U(x)$, is expressed as follows in the computational basis:\n", "$$ \\kappa(x_{i}, x_{j}) = |\\langle \\mathbf{0} | U^{\\dagger}(x_i) U(x_j) | \\mathbf{0} \\rangle|^{2} $$\n", "\n", "For identical input datasets, the kernel matrix is symmetric, positive-definite with 1s along the diagonal. Generally, we consider two types of kernel matrices. The symmetric training matrix in which the kernel matrix is constructed using two copies of the training set. And the test matrix in which the kernel matrix is constructed using the test set and the training set. For classification tasks, we can utilise a classical support vector machine whose parameters are optimised in order to linearly separate the data of interest.\n", "\n", "In this notebook, we will simulate a photonic processor to estimate a fidelity-based kernel. We then use this kernel function to classify an ad-hoc binary dataset." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Perceval for linear optics simulation\n", "from perceval import Circuit, BasicState, NoiseModel, Processor, PostSelect, pdisplay, Detector\n", "from perceval.algorithm import Sampler\n", "from perceval import catalog\n", "\n", "# Sci-kit learn for ML functionality\n", "from sklearn.model_selection import GridSearchCV\n", "from sklearn.svm import SVC\n", "\n", "# Maths packages\n", "import numpy as np\n", "from scipy.linalg import sqrtm, inv\n", "\n", "# Matplotlib for plotting graphs\n", "import matplotlib.pylab as plt\n", "\n", "# Pretty typing\n", "from typing import Callable, Tuple" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction\n", "\n", "Firstly, we consider an $m$-mode linear interferometer described by the matrix, $U$. We note that the probability of obtaining an output state, $|\\mathbf{t} \\rangle = |t_1, t_2, \\cdots , t_m \\rangle$, for a given input state, $|\\mathbf{s} \\rangle = |s_1, s_2, \\cdots , s_m \\rangle$ is:\n", "$$ |\\langle \\mathbf{s} | U | \\mathbf{t} \\rangle|^{2} = \\frac{|\\text{Per} \\left( U_{s, t} \\right)|^2}{s_1! ... s_m! \\ t_1! ... t_m!} $$\n", "\n", "where $\\text{Per}(U_{s, t})$ is the permanent of the matrix, $U_{s, t}$ which is constructed by repeating the $j^{th}$ column $t_{j}$ times, followed by repeating the $i^{th}$ row $s_{i}$ times [2]. First, we will define a function which will estimate the kernel associated with a given feature map, $U(x)$ and initial state, $| s \\rangle.$ \n", "$$ \\kappa^{(\\mathbf{s})}(x, x') = |\\langle \\mathbf{s} | U^{\\dagger}(x') U(x) | \\mathbf{s} \\rangle| ^ {2} = |\\langle \\mathbf{s} | U (x, x') | \\mathbf{s} \\rangle| ^ {2}$$\n", "\n", "We consider two possible kernels with indistinguishable and distinguishable photons. In the case of indistinguishable photons, we have the \"quantum kernel\":\n", "$$ \\kappa^{(\\mathbf{s})}_Q(x, x') = \\frac{|\\text{Per} \\left( U_{s, s} (x, x') \\right)|^2}{\\mathbf{s}!^2} $$\n", "\n", "where $\\mathbf{s}! = s_1! s_2! \\cdots s_M!$. We will also consider the kernel constructed using distinguishable photons. In the absence of indistinguishability, the photons do not undergo quantum interference. In this case, we have the \"coherent kernel\":\n", "$$ \\kappa_C^{(\\mathbf{s})}(x, x') = \\frac{\\text{Per} \\left( |U_{s, s} (x, x')|^{2} \\right)}{\\mathbf{s}!^2} $$\n", "\n", "In experimental circumstances, the experimentalist may not have access to photon-number resolving (PNR) detectors. In this instance, we consider the \"unbunching kernel\" in which there is maximum 1 photon found in each mode. In this circumstance, we lose the commutative property of the kernel function and the kernel matrix is not symmetric, positive definite for identical input datasets. We will force the kernel matrix to be symmetric by projecting the upper triangular onto the lower triangular. We will also force this matrix to be positive definite by decomposing the matrix spectrally:\n", "$$K = Q \\Lambda Q^{\\dagger}$$\n", "and setting the negative eigenvalues in $\\Lambda$ to $0$. The kernel matrix is then reconstructed." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def kernel(\n", " x1, x2: np.ndarray, \n", " feature_map: Callable[[np.ndarray], Circuit], \n", " input_state: BasicState, \n", " indistinguishability: float = 1.0, \n", " unbunching: bool = False, \n", " nshots: int = None\n", ") -> float:\n", " \"\"\"\n", " Estimate the kernel associated with a given feature map.\n", " Args:\n", " x1, x2 : Data inputs\n", " feature_map : Quantum circuit to perform feature map\n", " input_state : Initial state quantum feature map is applied to.\n", " indistinguishability : Photon indistinguishability (Hong Ou Mandel)\n", " unbunching : Specifies whether to not use PNR detectors\n", " nshots : Number of circuit runs to estimate kernel\n", " Returns\n", " Scalar kernel value\n", " \"\"\"\n", " # Compile circuit to estimate kernel\n", " U = feature_map(x1)\n", " U_dag = feature_map(x2)\n", " U_dag.inverse(h=True)\n", " processor = Processor(\"SLOS\", U.m)\n", " processor.add(0, U)\n", " processor.add(0, U_dag)\n", " processor.min_detected_photons_filter(input_state.n)\n", " processor.noise = NoiseModel(indistinguishability=indistinguishability)\n", " \n", " if unbunching:\n", " for m in range(processor.m):\n", " processor.add(m, Detector.threshold())\n", "\n", " # Apply settings to processor\n", " processor.with_input(input_state)\n", " sampler = Sampler(processor)\n", " \n", " if nshots is None:\n", " # Exact probability calculation\n", " prob_distribution = sampler.probs()\n", " results = prob_distribution['results']\n", " overlap = results[input_state]\n", " return overlap\n", " else:\n", " # Estimated probability of input_state\n", " sample_count = sampler.sample_count(nshots)\n", " results = sample_count['results']\n", " overlap = results[input_state] / nshots\n", " return overlap\n", "\n", "def kernel_matrix(X1, X2, feature_map, input_state, indistinguishability = 1.0,\n", " unbunching = False, nshots = None):\n", " \"\"\"Estimates the kernel matrix associated with specified feature map and pairwise \n", " datasets, X1, X2\"\"\" \n", " matrix_symmetry = np.array_equal(X1, X2) \n", " K = np.ones((len(X1), len(X2)))\n", " \n", " for i in range(len(X1)):\n", " # For symmetric matrix, calculate upper triangular only\n", " start_j = i + 1 if matrix_symmetry else 0 \n", " for j in range(start_j, len(X1)):\n", " if not np.array_equal(X1[i], X2[j]):\n", " K[i][j] = kernel(X1[i], X2[j], feature_map, input_state, indistinguishability, \n", " unbunching, nshots)\n", " if matrix_symmetry:\n", " K[j][i] = K[i][j]\n", " \n", " # Make unbunching training matrix positive\n", " if unbunching and matrix_symmetry:\n", " eigenval, eigenvec = np.linalg.eig(K)\n", " K = (eigenvec @ np.diag(np.maximum(0, eigenval)) @ eigenvec.T).real\n", " np.fill_diagonal(K, 1) # Ensure diagonals are equal to 1\n", " return K" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will consider an ad-hoc dataset in which the geometric difference between quantum and coherent kernel matrices is maximised. For a known unitary as feature map, Ref. [1] presents the following algorithm: \n", "\n", "*** \n", "**Input** \n", "- Feature map, $U$ with $m$ modes, \n", "- Dataset size, $N$, \n", "- Feature dimension, $d$, \n", "- Number of input photons, $n$, \n", "- Initial state, $s \\in \\Phi_{m, n}$, \n", "- Regularisation parameter $\\lambda \\neq 0$ \n", "\n", "**Output** \n", "- Dataset, $\\{x_i, y_i\\}_{i=1}^{N}, \\ x_i \\in \\mathbb{R}^{d}, \\ y_i \\in \\{1, -1 \\}$ \n", "\n", "*** \n", "1   Generate $N$ random datapoints, $\\{x_i\\}_{i=1}^{N}, x_i \\in \\mathbb{R}^{d}$ \n", "2   **while** $0 < i \\leq N$ **do** \n", "3       **while** $0 < j \\leq N$ **do** \n", "4           $K_{Q}(x_i, x_j) \\leftarrow |\\text{per}\\left(U(x_i, x_j) \\right)|^{2}$ \n", "5           $K_{C}(x_i, x_j) \\leftarrow \\text{per}\\left(|U(x_i, x_j)|^{2}\\right)$ \n", "6           $j \\leftarrow j + 1$ \n", "7       **end while** \n", "8       $i \\leftarrow i + 1$ \n", "9   **end while** \n", "10   $S \\leftarrow \\sqrt{K_Q} (K_C + \\lambda \\mathcal{I})^{-1} \\sqrt{K_Q}$ \n", "11   $g \\leftarrow$ max eigenvalue of $S$, $\\mathbf{v}_{g} \\leftarrow$ max eigenvector of $S$ \n", "12   $y \\leftarrow \\text{sign}(\\sqrt{K_Q} \\mathbf{\\mathbf{v}_{g}})$ \n", "\n", "*** " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def generate_data(\n", " feature_map: Callable[[np.ndarray], Circuit], \n", " input_state: BasicState, \n", " data_size: int, \n", " data_dim: int, \n", " reg: float\n", ") -> Tuple[np.ndarray, np.ndarray]:\n", " \"\"\"\n", " Generates ad hoc dataset for choice of feature map and initial state.\n", " Args: \n", " feature_map : Unitary to generate the dataset\n", " input_state : State on which the feature map acts on\n", " data_size : Dataset size\n", " data_dim : Feature dimension\n", " reg : Regularization parameter\n", " Returns:\n", " Data features, Data labels\n", " \"\"\"\n", " # Generate features dataset\n", " X = np.random.uniform(0, 2, size=(data_size, data_dim))\n", "\n", " # Construct quantum and coherent kernel matrices\n", " K_quantum = kernel_matrix(X, X, feature_map, input_state, indistinguishability=1)\n", " K_coherent = kernel_matrix(X, X, feature_map, input_state, indistinguishability=0)\n", " \n", " S = sqrtm(K_quantum) @ inv(K_coherent + reg * np.eye(data_size)) @ sqrtm(K_quantum) \n", " eigenvals, eigenvecs = np.linalg.eig(S)\n", " v_g = eigenvecs[:, np.argmax(eigenvals)]\n", " vector = sqrtm(K_quantum) @ v_g\n", " \n", " # Assign labels\n", " y = np.where(np.real(vector) >= 0, 1, -1)\n", " return X, y" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will generate a feature map which encodes each component of a feature vector into the phase shifters of our linear optics setup. Specifically, we let each pair of components be encoded into the phase shifters of a Mach-Zender interferometer (MZI): $\\phi_i \\rightarrow 2 \\pi x_i$.\n", "\n", "Each MZI is arranged in a brickwork pattern as shown in the figure below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "MZI\n", "\n", "\n", "Φ=0\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=4*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2*pi/5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=8*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2*pi/3\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=4*pi/5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=14*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "MZI\n", "\n", "\n", "Φ=16*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=6*pi/5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=4*pi/3\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=22*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=8*pi/5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=26*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=28*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=2*pi\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=2*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "MZI\n", "\n", "\n", "Φ=2*pi/5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=8*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=2*pi/3\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4*pi/5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=14*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=16*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=6*pi/5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=4*pi/3\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "MZI\n", "\n", "\n", "Φ=22*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=8*pi/5\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "MZI\n", "\n", "\n", "Φ=26*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "Φ=28*pi/15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "Rx\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def mach_zender(x1, x2):\n", " \"\"\"Mach Zender interferometer with phase shifter values, 2pi x1 and 2pi x2\"\"\"\n", " return catalog[\"mzi phase first\"].build_circuit(phi_a=2 * np.pi * x1, phi_b=2 * np.pi * x2)\n", "\n", "def brickwork(\n", " x: np.ndarray, \n", " num_modes: int, \n", ") -> Circuit:\n", " \"\"\"\n", " Generates brickwork Mach-Zender feature map for input number of modes\n", " and input data.\n", " Args:\n", " x : Data feature vector\n", " num_modes : Width of circuit\n", " Returns:\n", " Circuit\n", " \"\"\"\n", " circ = Circuit(num_modes)\n", " even_modes = np.arange(0, num_modes - 1, 2)\n", " odd_modes = np.arange(1, num_modes - 1, 2)\n", "\n", " # sub_index determines which mode MZI is applied to\n", " sub_index = 0\n", " for i in range(0, len(x) - 1, 2):\n", " # Select modes based on the cycle position\n", " cycle_position = i // 2 % (num_modes - 1)\n", " modes = even_modes if cycle_position < num_modes // 2 else odd_modes\n", " \n", " # Add MZI to the appropriate mode\n", " circ.add(int(modes[sub_index]), mach_zender(x[i], x[i + 1]))\n", " \n", " # Reset index which MZI is applied to\n", " sub_index = (sub_index + 1) % len(modes)\n", " return circ\n", "\n", "# Generate a sample brickwork circuit for sample input data x\n", "x = np.linspace(0, 2, 31)\n", "num_modes = 6\n", "pdisplay(brickwork(x, num_modes), recursive=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Training with Support Vector Classifiers\n", "\n", "Let us generate our dataset of interest using this brickwork feature map. As in Ref. [1], the dataset we will use is 30-dimensional, for which we generate 100 total datapoints." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "# Input parameters for data generation algorithm\n", "N = 100\n", "input_state = BasicState([1, 1, 0, 0])\n", "reg = 0.02\n", "feature_map = lambda x: brickwork(x, input_state.m)\n", "\n", "X, y = generate_data(feature_map,\n", " input_state,\n", " data_size=N,\n", " data_dim=30,\n", " reg=reg,\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now pre-calculate the quantum kernel matrix for the total dataset. Here, we consider 1.0 indistinguishability and PNR detectors." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "K_Q = kernel_matrix(X, X, \n", " feature_map,\n", " input_state, \n", " nshots=100_000,\n", " indistinguishability=1.0,\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pre-calculate the coherent kernel matrix with 0.0 indistinguishability and PNR detectors." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "K_C = kernel_matrix(X, X,\n", " feature_map,\n", " input_state,\n", " nshots=100_000,\n", " indistinguishability=0.0,\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's construct the unbunching kernel matrix with 1.0 indistinguishability and unbunching." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "K_U = kernel_matrix(X, X,\n", " feature_map,\n", " input_state,\n", " nshots=100_000,\n", " indistinguishability=1.0,\n", " unbunching=True,\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will then split the X, y data into training and test sets. We allocate 2/3 of the datapoints to the training set, and 1/3 to the test set. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Generate training and test indices to split matrices\n", "indices = np.arange(N)\n", "np.random.shuffle(indices)\n", "split_point = int(N * 0.66)\n", "train_indices = indices[:split_point]\n", "test_indices = indices[split_point:]\n", "\n", "# Train test split \n", "K_Q_train = np.array([[K_Q[i, j] for j in train_indices] for i in train_indices])\n", "K_Q_test = np.array([[K_Q[i, j] for j in train_indices] for i in test_indices])\n", "K_C_train = np.array([[K_C[i, j] for j in train_indices] for i in train_indices])\n", "K_C_test = np.array([[K_C[i, j] for j in train_indices] for i in test_indices])\n", "K_U_train = np.array([[K_U[i, j] for j in train_indices] for i in train_indices])\n", "K_U_test = np.array([[K_U[i, j] for j in train_indices] for i in test_indices])\n", "\n", "X_train, X_test = X[train_indices], X[test_indices]\n", "y_train, y_test = y[train_indices], y[test_indices]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's visualise the three different training and test matrices." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "image/png": "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QkBAAObPn7/fbeTl5eHuu+/GtGnTkJqaCn9/f4SHh2PSpEl45JFHsHfvXvP+trzSBvvS2vJ7x76srq7GL37xC2RlZcHf39+sK7zt/3333QcfHx/k5OTA7XY72vzYY4/Bx8cHKSkpqKys7G53HRF69Q2t119/HXv37kV4eDhOP/30Tt/r4+ODCy+8EACwcOHCQ1KrWlZWhgsvvBBvvvkmIiIiMHv2bEybNg319fV46KGHMHLkSGzbtm2/n1+0aBEmTJiATZs2YcaMGTjuuOOwZcsW3HjjjbjuuuvMey+66CKMGDECwL5f1Vx00UXt/02aNOk770sbb775Jk444QSUlJRgxowZyM7OxieffIIZM2Zg6dKlePjhh3HqqaeiqakJM2fORFJSEhYvXowpU6Z0uq+Hgq1bt2LUqFF46623MGHCBJx++unmL6bnnnsu/v3vfyMgIADTpk3DrFmz0KdPHzz55JMYM2ZMp0lyf9TV1WHixIn4xz/+gSFDhuDkk0+Gx+PBM888g+OPPx51dXUHvM077rgD55xzDgDglFNOwYABA7B06VKcdtppePXVVx3v37BhA8aOHYunn34avr6+mDt3LgYOHIj77rsPM2bM6FTo3h0uuOAC9O/f3/GXCbfbjZdeegmpqak46aST9vv5tWvX4oorrsCnn36K+Ph4zJkzB5MmTUJJSQn+8Ic/YNy4caiqqmp/f9tip61OfdasWWY8Z2dnm+17PB7MmzcPt956a3vSGT58eKf7dOyxx+KPf/wjmpqacO6555qJ3O1245xzzsGuXbvwhz/8Accff3y3+0ocnbzxxhvYs2dPt3KNNzweDy666CLMnz8fn3zyCUaNGoV58+a1n3ejRo3CO++8s9/PP/HEE5g+fTqqq6sxe/ZsjBw5EqtXr8ZFF12Ev/zlL473t7a24rzzzsM555yDjz76CDk5OTjjjDMQExOD5557DmPHjt3v93V1vr300ksYMWIEHn30UfTr1w+nnHIKxo4di1WrVuHCCy/s9Ob3DzHndaTtDwT3338/MjIycPrpp2PAgAHtr992222477770NTUhDFjxuCMM85AcnIy3njjDcyYMaP9l+AHyiWXXIKrrroK4eHhOO200xAfH4/33nsPU6dOxRdffHHA2zuQfgb23cyaOnUqfve736G0tBQzZ87EpEmT8M4772D06NHIy8s7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xZHnoYceQnZ2NnJzczFp0iSvjordu3fjiSeewKhRo8wFwo033ggA+N///V+sXbu2/d89Hg9+97vfYc2aNQgPD8f/+3//7zu3My4uDldffTUaGhowZ84cfPXVV473NDc3Y+HChe2uvO5yxx13oF+/frjpppvw9NNPey1HWb9+Pf7zn/8cdPs7crjniAOhLcc8/fTT5tcMTU1N+PnPf77fX0b80AkMDMTll18OALjuuuuMk7S5uRlXXXVVl+VTB0JycjLKy8tRWVnZvi7rioPN74d7/DQ1NeGcc86B2+3Gr3/9a0yfPh2hoaHmDyf7Wz+K3snNN98MHx8fPPfcc3j88ccdr3/++eftD/644YYb9uvPO1iKi4tx4403mgdfbdy4EXfddRcAtD8cp42Dvf45WK6//nqEhIRg4cKF+NWvftX+UI02ysvLu/Q/HSnaftV/xx13YMuWLe3/vnfvXtx6661H/AcD32U9/kPnsssuQ2BgID799FM8/PDD5rXPPvsMf/vb3w7p9z3wwAOorKzEjh07vOoTmLYc9f7775vzCNh33+GFF17Y72cPd55asGABHn30UcTFxWHBggXo06cP/ud//gdnn302cnNzu/ULtB8Cvf6GVmRkJJYsWYIxY8Zg9erVGDZsGMaPH4/58+dj7ty5SExMxA033ICgoCA88sgjXt0fxx9/PObOnYv6+nqMHTsWM2fOxNy5c5GZmYlHHnlkv4uuO+64Az4+Pvj1r3+N4cOHY/78+Zg+fTqGDRuGzMxMTJw48ZDu66xZsxAUFIT//ve/mDRpEi655BJcfvnlePLJJ9vfc9tttyE8PBxvvfUWcnJycPbZZ2PKlCkYMmQIoqOjD3mbfijcdttt6NevHx577DEMHDgQ5513Hk4++WRkZWWhubn5gEqEfkj4+PjgX//6FyIjI/Hcc88hMzMT5513HubMmYOcnBxERka2l2y0/TX6+8TX1xe/+c1vAAA/+clPcOyxx+KCCy7ApEmTMHHiRJx22mle/6oOfPsktZtvvhlz5szBZZddhssvvxxLly79zu3atWsXzjnnHNTX17dfaLTREydyceSJiIjAZ599hhNPPBEbN27ECSecgMzMTJxxxhm44IILMH369PZfNtbX1xvp7k9/+lNceOGF7TeKTzrpJFxwwQUYPHgwfvOb3yAgIAALFiw4JA8rAYA//vGP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AxPDSK/RAdXV1fa5z33OzjjjjCO0pcDEM9Lrs7e318477zyrrq623/72t7Z161a74447rKSk5AhvOTAxjPQa/cUvfmHXXXedrV271jZv3mw//elP7de//rVdf/31R3jLgfGvq6vLjj32WLv99tvjWr6qqsre8Y532Fve8hZ76aWX7Morr7SPf/zj9sADD4zoc5NisVhsJL9w0kkn2QknnGC33XabmZn19/dbaWmpffrTn7brrrsusPzFF19sXV1dds899xz6t5NPPtlWrlxpP/zhD0e0sQCGNtLrUx04cMBycnLstttusw9/+MOHe3OBCWc01+iBAwfszDPPtI997GO2bt06a29vj+u/egEYmZFenz/84Q/t3//9323Lli2WkpJypDcXmHBGeo1eccUVtnnzZnv44YcP/dtnP/tZe/bZZ+2JJ544YtsNTDRJSUl21113DTmr4Nprr7V7773XfdHpgx/8oLW3t9v9998f92eN6Btavb299uKLL9qaNWv+uoJJk2zNmjX29NNPh/7O008/7ZY3Mzv//PPfcHkAozOa61Pt3bvX+vr6bMaMGYdrM4EJa7TX6Je//GXLz8+3yy+//EhsJjAhjeb6/MMf/mCnnHKKfepTn7KCggJbtmyZff3rX7cDBw4cqc0GJozRXKOnnnqqvfjii4emJVZWVtp9991nF1xwwRHZZgBvbKzeEyWPZOHW1lY7cOCAFRQUuH8vKCiwLVu2hP5OU1NT6PJNTU0j2lAAQxvN9amuvfZaKy4uDgwuAP52o7lGn3jiCfvpT39qL7300hHYQmDiGs31WVlZaY888oh96EMfsvvuu88qKirsn//5n62vr8/Wrl17JDYbmDBGc41eeuml1traaqeffrrFYjHbv3+/ffKTn2TKIXAUeKP3RJFIxPbt22dTp06Naz38lUMAZmZ200032a9+9Su76667LC0t7c3eHGDCi0ajdtlll9kdd9xhubm5b/bmABD9/f2Wn59vP/7xj2316tV28cUX2w033ECkBnCUeOyxx+zrX/+6ff/737f169fb7373O7v33nvtK1/5ypu9aQDGyIi+oZWbm2uTJ0+25uZm9+/Nzc1WWFgY+juFhYUjWh7A6Izm+hxw880320033WQPPfSQrVix4nBuJjBhXvp2FwAAA7VJREFUjfQa3bFjh1VXV9uFF1546N/6+/vNzCw5Odm2bt1q8+bNO7wbDUwQo6mhRUVFlpKSYpMnTz70b0uWLLGmpibr7e21KVOmHNZtBiaS0VyjX/jCF+yyyy6zj3/842Zmtnz5cuvq6rJPfOITdsMNN9ikSXy3A3izvNF7oszMzLi/nWU2wm9oTZkyxVavXu2C9fr7++3hhx+2U045JfR3TjnlFLe8mdmDDz74hssDGJ3RXJ9mZt/61rfsK1/5it1///12/PHHH4lNBSakkV6jixcvto0bN9pLL7106H/vete7Dv01mNLS0iO5+cC4Npoaetppp1lFRcWhF81mZtu2bbOioiJeZgFjbDTX6N69ewMvrQZeQI/w76IBGGNj9p4oNkK/+tWvYqmpqbE777wztmnTptgnPvGJWHZ2dqypqSkWi8Vil112Wey66647tPyTTz4ZS05Ojt18882xzZs3x9auXRtLSUmJbdy4caQfDWAYI70+b7rpptiUKVNiv/3tb2ONjY2H/heNRt+sXQDGtZFeo+ojH/lI7N3vfvcR2lpgYhnp9VlbWxvLyMiIXXHFFbGtW7fG7rnnnlh+fn7sq1/96pu1C8C4NtJrdO3atbGMjIzYL3/5y1hlZWXsz3/+c2zevHmxD3zgA2/WLgDjVjQajW3YsCG2YcOGmJnFvvOd78Q2bNgQq6mpicVisdh1110Xu+yyyw4tX1lZGZs2bVrs85//fGzz5s2x22+/PTZ58uTY/fffP6LPHdGUQzOziy++2FpaWuyLX/yiNTU12cqVK+3+++8/FOhVW1vr3oSfeuqp9otf/MJuvPFGu/76623BggV2991327Jly0b60QCGMdLr8wc/+IH19vba+9//freetWvX2pe+9KUjuenAhDDSaxTAkTPS67O0tNQeeOABu+qqq2zFihVWUlJin/nMZ+zaa699s3YBGNdGeo3eeOONlpSUZDfeeKM1NDRYXl6eXXjhhfa1r33tzdoFYNx64YUX7C1vecuh9tVXX21mZh/5yEfszjvvtMbGRqutrT308/Lycrv33nvtqquusltvvdVmzZplP/nJT+z8888f0ecmxWJ83xIAAAAAAACJg/8MDAAAAAAAgITCCy0AAAAAAAAkFF5oAQAAAAAAIKHwQgsAAAAAAAAJhRdaAAAAAAAASCi80AIAAAAAAEBC4YUWAAAAAAAAEgovtAAAAAAAAJBQeKEFAAAAAACAhMILLQAAAAAAACQUXmgBAAAAAAAgofz/t3pDACpWuWEAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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5vM//vjjDoCrUaOG27p1a5GumRCJwsMPP+wAuMzMTHfo0KFjrh8EgbvkkkscAFeiRAnXvXt3N2zYMNegQQMHwJUuXdp98MEHpl5WVpYD4O644w7neZ5r06aNGzZsmOvQoUP8OX3wwQdNvYMHD7qhQ4c6AC45Odl17NjRDRkyJL4GlipVyuTLnwtr1arl+vXr55KSklyPHj3c8OHDXfPmzePlxo0b51JSUhwA16hRIzdgwADXvXt3l5qa6gC4UaNGmf7kz4+33XZbfJ0bNmyYO+OMM1wkEnEA3PXXX1+ozogRI+L9bdGihRsxYkT8vyeeeOKY74Fzh+e2I9e/47len376qStZsmS8f4MHD3YDBgxwp556qktOTnb9+/ePl73xxhtdp06dHABXt27dQp/lT3/6U5H6nj/Pjxkzxu3YscOlpKS4+vXrFyoTjUZdamqqq1WrlovFYoXWpYJ88cUXDoCrWLGi69y5szv//PNdr169XPny5R0AV69evULr6sKFC92IESNc5cqVHQB31llnFfoMX3zxhXOu6GMobB9y//3303UmGo3Gn5W//vWvRbpeQvzYFNxP5u+7wsif24987zieudK5w+8xv/jFL1xKSorLzs6OP9dJSUkOgBs8eLB5tzje95/8+HnnneeaN2/uMjMzXd++fV3//v1dpUqVHACXlZXldu3aZdr89NNPXWZmpgPgKlWq5Pr37++GDBni2rVr50qWLGly5V/TO++803me5zp16uTOP//8+PzseZ574403TJ6weSb/M19//fUuIyPDZWVluaFDh7qePXu6UqVKxT/XkcRiMXfuuec6AC4pKcn16tXLnX/++a5OnTqudOnS7pprrqHX6ljIn+cL7snzmTx5sitbtqwD4LKzs12/fv3cWWedFY/16tXLHThwoFB/e/To4QC4jIwM16dPHzd8+HDXs2fP+PibNWuWc+77NWHEiBHxdXzQoEGF5viFCxcWqf8F32tuueUWB8C98MILhcrccccdDoB74okn4u9+devWNW316NHD+b7vmjVr5s4++2w3ZMgQ17p163iOhx56qFD5P/3pT+6ss85yAFzlypUL9f/GG2+Ml8u//5dddplLTk522dnZbujQoa5v377uvvvuc84dfic98j7s2LEjfu3+8Y9/FPrZY4895gC4mjVrhr6X/hzQgdZx8NBDD8VfMg4e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dmjatKmJV6tWDX/9618BIN7u8VCyZEk8/vjjyMjIOKZ6mZmZGDduHJKSkvDLX/4Ss2fPxqWXXoqlS5eib9++uPHGG4+7T0L8r7Ju3TosX74cAHdw9Twvrq8UNuf37duXxhs3bgwAhbRKJk6ciL1796JTp06oXr06rZc/B4VplTD3vCAI8MEHHwAAzj//fFqvbdu2SEtLw6xZs4xuxrF+jh+L471ep556KgDgwgsvxJdffkn1S04mPXr0QFZWFl599VXk5uZi4cKF+Oqrr9CtW7ci261PnToVf/nLX3D11Vdj1KhRGDlyJK666iokJSVh69at2Llz53H3L8yB8WgUXGduuOEG3HDDDShZsiTGjRt3VO06If5XON65cujQoUhJSTHx/LVn6dKl2LBhQzH18vs5v2rVqkXu54cffggAuPzyy485F7smycnJqFOnDs11NM4991yjDwnwvq9btw4rVqwAAFxwwQWmTlJSEgYPHnxM+Y+F999/H0D42lu9enXUr18fW7duxdKlSwEArVu3RiQSwVNPPYX/+7//+0mcl0ePHg3nXFxXuCgajwXZvn07nnvuOdxyyy247LLLMHLkSIwcOTKu57h48eLj7tt5552HSCRyXHWvu+46DBo0CMuWLcMpp5yCZcuWoV+/fnoPglwOfxScc/F/M3HE42XOnDmYMGECVq5ciby8PARBAOB78cAgCLBs2TK0atXK1PuhBevDDz/8STb3Z599tomVK1cO5cuXx/bt2+nP69evDwDFukjmkz+JDxo0CCVK2MfE93106dIF8+fPx9SpU9G0aVNUrFgR2dnZmDt3Lm688UaMGTMGTZo0Kfa+/RCjRo3CPffcg6eeegojRozASy+9hL1798bFCI/G/v378fHHH2PGjBnYsmUL9u/fD+dc/HD0RCbxVq1axTcAx0q7du1w33334brrrkPXrl2xe/duZGVlxYWOhfhfpGLFigCALVu2HHPd/Hm8fPnyoYfIdevWLVT2SMLcmvLbK3h4lL/pnjBhwlGfSfaLnUqVKlGr6u3btyMajQIAatas+YPt5pc/8oDoWD7Hj8XxXq8//elPmDt3Lj744AN88MEHcaH+rl274sILL4y/FJ0s8sWH7777brzyyitxs5uivChs2bIFgwYNwpdffvmD5aLRKMqWLXvMfQsbQ0Wlbdu2GDt2bPyXaX/5y1/Qvn37425PiB+DgvNHwfcNRv7Pw+ac450ra9euTePp6enxffy6deuMO+rxcqz9zBcdP55fKhf3+nEs7a1btw4AUKFCBaSlpdF6Rf1FwvGQv06dfvrpRy27detWNGjQAHXr1sWDDz6Im2++Gddccw2uueYaZGVl4bTTTsO5556LIUOGmC9kFDdDhw7F9ddfj2effRbXXnst3n77bdSvX79In+OJJ57ADTfcgLy8vNAy+XuS4+FE79dTTz2FyZMnY9u2bahWrVqoscLPDR1oHQcVKlQAAGzevLlI5Qu+jOS/oJwIeXl5uPjii/Hmm2/+YLmwB+6/cXMf1qe0tDRs376d/jz/W1NhriAnQv4kfscdd+COO+74wbIFXzaee+45DB48GA888EDclax9+/Y488wzcfHFF8fHzsmibt266NKlCz7//HMsX74cTz31FHzfL5L7yVdffYXzzz8fa9asCS3zU07i1157Ld577z18/PHH8DwPL7/88nG99AiRKLRp0wbPP/88vv32W8RiseP+rd7x4vtF/xJ3/i9U6tWrh06dOv1gWfZSUapUqR9sF+DfNDuS5ORkEzuWz/FjcbzXq0qVKpg5cyYmT56MTz/9FFOmTMHXX3+NKVOm4J577sGf/vQn3HrrrSe176NGjcLvfvc7PP7441i9ejXKlCmDgQMHHrXepZdeii+//BKnnXYa7r77brRo0QJly5ZFyZIlAXz/beCNGzcet0tT2BgqKvv27Sv0LeSvv/76hNoT4scgNTU1/u8fegkHgNzcXAAIPRw5mXPlsTzXBed9xo85pxd3ruNp74d+6XEyf6mbfx8GDx5caJwxypcvH//3tddei6FDh+Kdd97Bl19+iS+//BIvv/wyXn75ZYwdOxZffPEF/YZdcZGWloYhQ4bg6aefxujRo7F///74N9J/iG+++QZXXHEFIpEI/vKXv6Bv376oVasWSpcuDc/z8Pjjj+OKK644ISfBE12nPvjgA2zbtg0AsG3bNixfvhxt27Y9oTb/F9CB1nHQpk0bvPDCC/j2229x6NAh+g2egkyfPh0AUKZMmdDfYjDCJvTbb78db775Jho1aoQ///nPaNeuHSpUqBA/8e7YsSOmTZsW+sD9FJv7E12cfuw+5/e3c+fO8W8xhHHKKafE/3366adj1apVeP/99zF58mRMnToVH330ET744AOMHTsWb775ZqE/nTwZjB49GpMnT8YNN9yAmTNnolevXkf9ZsOePXtw3nnnYfPmzRg1ahR+8YtfoF69esjIyEAkEsGSJUvQsGHDn3QSX7p0KaZNmwbg+43R9OnT0aFDhxNqU4j/Zs4991z86le/wq5du/DOO+9gwIABRa6b/y2l/G84sW9p5R/ch/3J27GQP8c0bNiwWH9jWKFCBZQqVQp79+7Ffffdd9J/KfBjcSLXK9/WO/9PEvft24dnnnkGV199NX79619j8ODBR123ToSsrCx07949/uf+V1555VHn97y8PIwfPx6+72P8+PHIzMw0P9+0adPJ6nKRyP+T9jPOOAPr1q3DG2+8gb/97W+47rrrftJ+CfFDlCtXDmlpacjNzcWyZcuodAQA7NixAzt27AAQ/kvk42XlypU0npOTg+3btwMAatSoEY/nv6+ESaPkf6OquKhVqxYWL16MRYsWoV69esXa9skkf23eunUr8vLy6KHSqlWrTlr+mjVrYunSpbj11luP+dCkcuXKuOyyy3DZZZcBABYtWoTRo0dj2rRpuO222/Dss8+ejC7HGT16NJ5++mm8++67iEQiRfqF2KuvvgrnHK699lrccsst5uf5f1b5U7F06VJcdtll8H0fl1xyCZ555hkMHToUs2bNQpkyZX7Svv3U/Pf92jIB6Nu3L3zfx+7du/H222//YFnnHJ5//nkAQP/+/QsdzBzvhD5u3DgAwCuvvIL+/fujWrVqhb6++VM8cPm/Yf2xFqeTTf7LRv/+/fHMM8/84H/nnXdeobqlSpXC4MGD8cgjj+Cbb77Bpk2bcPnllyMnJ6fIf799IgwePBgZGRl49913ARTtT0E+//xzbN68Ga1bt8ZTTz2Fdu3aoWzZsvFvhPzUk/i+ffswdOhQ5OTk4MILL0RycjJuvvlmzJw58yftlxAnk7p162L48OEAgBtvvDH+MhLGli1b4n8WXKNGjfihBjswcc7F4926dTvhvvbo0QNJSUmYNGnScf2JZBiRSARnnnkmgMNr38kmfz09mfpUxXm9UlJScOWVV6J58+YIggBz586N/+xkfZbLL78c5cuXR/ny5TFmzJijlt+9ezdisRgyMjLMYRYAvPDCC6G/MPkx7se///1vPPbYY6hcuTJefvlljBs3TuuMSAh838cZZ5wBAHj99ddDy7322msAgLJly6Jly5bF2odXX32V/rVE/vtPvXr1Cv3iJP/fCxcuNHWcc3HdxOKid+/eAL7/c7JEombNmvG/bvj3v/9tfn7gwIEfvOcnSp8+fQAUz9rbqFGj+LeHZ8+eXehnJ2OO79y5M9q2bYvy5ctj4MCBRfpz1/w9VlZWlvnZvn37Qq/1j7FG7du3D0OGDEFOTg5++9vf4umnn8aQIUOwcuXKH+Xd8r8dHWgdB3Xr1sXQoUMBADfffDN27doVWvbRRx/F3LlzkZSUZE57j3dC/6EH7qOPPop/FbG4KMqD+kOfZe7cuVi7dm2x9ulkkz+J55/WnwgVK1aMi6qvWbOmkODtyZgES5cujZEjR6J8+fKoXbu2OXBjHO23di+88EJo3fzDzJM5kV9//fWYPXs2unXrhueeew73338/Dhw4gKFDh/7g8ydEovPII4+gXr16WLlyJTp37kz1hw4cOICnnnoKrVq1KjQH33TTTQCA3//+95gzZ0487pzDH/7wB8yePRuZmZnx36CeCJUrV8a1116LvLw89O3bF/PmzTNl9u/fj3feeSeuu1RUxo4di6SkJNx888149tln6Td+58+fjzfeeOO4+1+Q/G8SfPfdd8XSHuN4r9d9991H/yx80aJF8V88FNwb5H+WBQsWFGv/hw4dim3btmHbtm1F+s195cqVUbZsWezatSv+kpvPV199hdtvvz207sm+H4sXL8bll18O3/fx4osvokqVKmjdurXWGZEw3HLLLfA8Dy+++CL+9a9/mZ9PmzYtbqR04403xvdtxcWGDRtw0003FdIJXrhwIX73u98BAG644YZC5Xv27Ang+wOvgnPTwYMHceutt2LGjBnF2r9f/epXSE9PxzvvvIPf/va3OHjwYKGfb9my5ajafj8V+d8QHTt2LJYsWRKPB0GA22+//aS+X918883IzMzEAw88EJ8Pj2TlypWF3hE+++wzjB8/3lxj5xzee+89APb99WTN8TNmzMC2bduKfCCXr0H57LPPFvqCxr59+3DVVVeFfhMxv/9Lly41n7u4uO666zBnzhx0794dY8eOBQA8+eSTqFu3Lt544w08/PDDJyVvoqADrePk//7v/5CdnY2VK1eie/fu5iE8dOgQHnjgAVx//fUAgMcff7zQn6YBxz+h5z9wjzzySKH44sWLceWVV57YByMUZaLJ/yx33313od/SrFq1CiNGjDjh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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))\n", "im1 = ax1.imshow(K_Q_train, cmap='Blues', vmin=0, vmax=1)\n", "ax1.set_title('Quantum Training Matrix', fontsize=16)\n", "ax1.set_xticks([])\n", "ax1.set_yticks([])\n", "\n", "# Plot Coherent training matrix\n", "im2 = ax2.imshow(K_C_train, cmap='Blues', vmin=0, vmax=1)\n", "ax2.set_title('Coherent Training Matrix', fontsize=16)\n", "ax2.set_xticks([])\n", "ax2.set_yticks([])\n", "\n", "im3 = ax3.imshow(K_U_train, cmap='Blues', vmin=0, vmax=1)\n", "ax3.set_title('Unbunching Training Matrix', fontsize=16)\n", "ax3.set_xticks([])\n", "ax3.set_yticks([])\n", "\n", "cbar = fig.colorbar(im1, ax=[ax1, ax2, ax3], orientation='horizontal', location='bottom', aspect=50)\n", "\n", "fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))\n", "im1 = ax1.imshow(K_Q_test, cmap='Reds', vmin=0, vmax=1)\n", "ax1.set_title('Quantum Test Matrix', fontsize=16)\n", "ax1.set_xticks([])\n", "ax1.set_yticks([])\n", "\n", "# Plot Coherent training matrix\n", "im2 = ax2.imshow(K_C_test, cmap='Reds', vmin=0, vmax=1)\n", "ax2.set_title('Coherent Test Matrix', fontsize=16)\n", "ax2.set_xticks([])\n", "ax2.set_yticks([])\n", "\n", "im3 = ax3.imshow(K_U_test, cmap='Reds', vmin=0, vmax=1)\n", "ax3.set_title('Unbunching Test Matrix', fontsize=16)\n", "ax3.set_xticks([])\n", "ax3.set_yticks([])\n", "\n", "cbar = fig.colorbar(im1, ax=[ax1, ax2, ax3], orientation='horizontal', location='bottom', aspect=50)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's fit SVC models with our training and test data for the three different kernels. We will also consider the completely classical Gaussian kernel for which, we will perform cross-validation to maximise its performance." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The SVC training accuracy using the quantum kernel 0.9696969696969697\n", "The SVC test accuracy using the quantum kernel 0.9696969696969697 \n", "\n", "The SVC training accuracy using the coherent kernel 0.9545454545454546\n", "The SVC test accuracy using the coherent kernel 0.6764705882352942 \n", "\n", "The SVC training accuracy using the unbunching kernel 0.9545454545454546\n", "The SVC test accuracy using the unbunching kernel 0.7941176470588235 \n", "\n", "The SVC training accuracy using the Gaussian kernel 0.9545454545454546\n", "The SVC test accuracy using the Gaussian kernel 0.47058823529411764\n" ] } ], "source": [ "# Train SVC with quantum matrix\n", "model_Q = SVC(kernel='precomputed')\n", "model_Q.fit(K_Q_train, y_train)\n", "print('The SVC training accuracy using the quantum kernel', model_Q.score(K_Q_train, y_train))\n", "print('The SVC test accuracy using the quantum kernel', model_Q.score(K_Q_train, y_train), '\\n')\n", "\n", "# Train SVC with coherent matrix\n", "model_C = SVC(kernel='precomputed')\n", "model_C.fit(K_C_train, y_train)\n", "accuracy_C = model_C.score(K_C_test, y_test)\n", "print('The SVC training accuracy using the coherent kernel', model_C.score(K_C_train, y_train))\n", "print('The SVC test accuracy using the coherent kernel', accuracy_C, '\\n')\n", "\n", "# Train SVC with unbunching matrix\n", "model_U = SVC(kernel='precomputed')\n", "model_U.fit(K_U_train, y_train)\n", "accuracy_U = model_C.score(K_U_test, y_test)\n", "print('The SVC training accuracy using the unbunching kernel', model_U.score(K_U_train, y_train))\n", "print('The SVC test accuracy using the unbunching kernel', accuracy_U, '\\n')\n", "\n", "# Gaussian kernel\n", "param_grid = {\n", " 'C': [0.1, 1, 10, 100],\n", " 'gamma': [1, 0.1, 0.01, 0.001]\n", "}\n", "grid_search = GridSearchCV(SVC(kernel='rbf'), param_grid, cv=5, scoring='accuracy')\n", "grid_search.fit(X_train, y_train)\n", "best_model = grid_search.best_estimator_\n", "accuracy_G = grid_search.best_estimator_.score(X_test, y_test)\n", "print('The SVC training accuracy using the Gaussian kernel', best_model.score(X_train, y_train))\n", "print('The SVC test accuracy using the Gaussian kernel', best_model.score(X_test, y_test))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Plotting Training Curves\n", "\n", "We will investigate the performance of the different kernels by plotting the test set accuracy versus the training set size." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "# Different training set sizes\n", "data_sizes = np.arange(20, N, 10)\n", "\n", "# Lists to contain accuracy for each kernel\n", "accuracies_Q = []\n", "accuracies_C = []\n", "accuracies_U = []\n", "accuracies_G = []\n", "\n", "for size in data_sizes:\n", " accuracy_sublist_Q = []\n", " accuracy_sublist_C = []\n", " accuracy_sublist_U = []\n", " accuracy_sublist_G = []\n", " \n", " for rep in range(10): \n", " # Generate indices for Train test split\n", " np.random.shuffle(indices)\n", " train_indices = indices[:size]\n", " test_indices = indices[-10:] # Constant test set of size 10\n", " \n", " # Train test split \n", " K_Q_train = np.array([[K_Q[i, j] for j in train_indices] for i in train_indices])\n", " K_Q_test = np.array([[K_Q[i, j] for j in train_indices] for i in test_indices])\n", " K_C_train = np.array([[K_C[i, j] for j in train_indices] for i in train_indices])\n", " K_C_test = np.array([[K_C[i, j] for j in train_indices] for i in test_indices])\n", " K_U_train = np.array([[K_U[i, j] for j in train_indices] for i in train_indices])\n", " K_U_test = np.array([[K_U[i, j] for j in train_indices] for i in test_indices])\n", " \n", " X_train, X_test = X[train_indices], X[test_indices]\n", " y_train, y_test = y[train_indices], y[test_indices]\n", " \n", " # Fit model with quantum kernel\n", " model_Q = SVC(kernel='precomputed')\n", " model_Q.fit(K_Q_train, y_train)\n", " accuracy_sublist_Q.append(model_Q.score(K_Q_test, y_test))\n", " \n", " # Fit model with coherent kernel\n", " model_C = SVC(kernel='precomputed')\n", " model_C.fit(K_C_train, y_train)\n", " accuracy_sublist_C.append(model_C.score(K_C_test, y_test))\n", " \n", " # Fit model with unbunching kernel\n", " model_U = SVC(kernel='precomputed')\n", " model_U.fit(K_U_train, y_train)\n", " accuracy_sublist_U.append(model_U.score(K_U_test, y_test))\n", " \n", " # Cross-validation grid search to obtain optimal gaussian kernel\n", " grid_search = GridSearchCV(SVC(kernel='rbf'), param_grid, cv=4, scoring='accuracy')\n", " grid_search.fit(X_train, y_train)\n", " best_model = grid_search.best_estimator_\n", " accuracy_sublist_G.append(best_model.score(X[test_indices], y_test))\n", " \n", " accuracies_Q.append(accuracy_sublist_Q)\n", " accuracies_C.append(accuracy_sublist_C)\n", " accuracies_U.append(accuracy_sublist_U)\n", " accuracies_G.append(accuracy_sublist_G)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "image/png": "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qMGRI0dYs2YN+/bt459//sHFxSVL55wyZQrTp0+nadOmdOrUiUuXLnH48GFefvllNm/eTLdu3Uz2v3DhAv7+/oSHh+Pr68srr7xCXFwc8+bNY/fu3ej1+iw9vl6vp3Pnzvz11184OzvTokULXF1dCQsL48qVK8ydO5fevXtTokQJ4zHBwcF07NiRc+fOUaJECRo0aICTkxOnTp1i7ty5bNq0ib1791K2bFkAOnbsiK2tLTt27MDLy4uOHTsaz5XZFptBgwaxatUqbG1tad68OR4eHkRERHD9+nUWL15M27ZtM0w2KlWqlOqYFoC7d+/yv//9DwALCwtjeUxMDF27dmXv3r04OjpSr149PDw8OHfuHMuXL2fTpk3s2rWLF154IVPPQwghRAGhCCEKlT179iiAktrbd8qUKcZtb731lpKQkJDqOdavX6/ExsaalOn1emXJkiUKoDz//POKXq9P9dxTpkwxKV+1apXxMV988UUlKirKuC0iIkKpU6eOAiizZs0yOe7GjRsKoJQtW9YsvuTzubq6KkePHk01jipVqpgdV7duXQVQ3njjDZPnfufOHaVq1arG8+7ZsyfVennavn37FEB54YUXlOjoaLPtJ06cUMLDw4339Xq90qxZMwVQBg4caHKMRqNRxowZowBK69atTc6T/H/q7++fqbhSCgoKUgCldOnSSkhIiNn2CxcuKEFBQSZl/v7+ma6HyMhI5fnnn1cAZezYsSbbevfurQDKyy+/rISGhpps++qrrxRAqVy5sqLVarP8vIQQQuQf6WIkRBFUokQJFi9ejI2NTarbe/XqhYODg0mZSqVi2LBhNGnShPPnz3Px4sUsPaaDgwOrVq3C2dnZWObm5sa4ceMAjFegs2LatGk0atTIpGz8+PG4uLjw33//mXRhOXDgAKdOncLR0ZElS5aYPPdSpUoxb968LD9+aGgoAC1atMDJyclse/369SlZsqTx/o4dOzh06BB16tRh+fLlJsdYWlry+eefU6NGDfbs2cO///6b5XjSi7Fu3bp4e3ubba9evXq2xxwkJSXRrVs3zp8/T69evfj888+N2y5evMi6devw9fXlxx9/xNPT0+TYUaNG0alTJ65cucIff/yRrccXQgiRPyRBEKIIateuXYbdea5evcrixYsZNWoUAwcOJCAggICAAOMPzrTGIqSlfv36+Pj4mJVXr14dINVxCBnp0qWLWZmNjQ0VKlQwO+e+ffsAQ5edlF1+knXu3BlXV9csPX7dunWxsLDgu+++Y8mSJYSEhKS7/7Zt2wB47bXXsLQ078GpVqtp2bIlAIcPH85SLGmpVq0aTk5ObN++nZkzZ3Ljxo0cOa+iKAQEBLB3715atmzJ6tWrjeNCALZv346iKLz00kupJk+AcU2OnHquQggh8oaMQRCiCEqvv7lOp2PEiBF8/fXX6U6TGh0dnaXHTOsqdXKLQkJCQpbOl9Vz3rlzB0j/uZctW5aHDx9m+vErVqzIV199xYcffsiIESMYMWIEZcuWpUmTJrz88sv06NEDa2tr4/7Xr18H4JNPPuGTTz5J99xhYWGZjiM9Tk5OrFq1iv79+zNp0iQmTZqEj48PjRs3pmPHjvTu3RtHR8csn3f8+PGsW7eO5557ji1btpi1RiU/12+//ZZvv/023XPl1HMVQgiRNyRBEKIIsrOzS3PbggULWL58Od7e3nz55Zc0bdoULy8vbG1tAejduzfr1q3L8hoLanXON0hm55wpr3JnZVtaRo4cSc+ePdm6dSsHDx7k4MGDrF+/nvXr1zNlyhQOHDhgbDlJHgTdvHnzDNd2eP7557McS1pee+012rVrx9atWzlw4ACHDh3il19+4ZdffmHy5Mns2rWLmjVrZvp8y5YtY86cOfj4+LB9+3bc3NzM9kl+rnXq1KF27drpnu/pbmJCCCEKNkkQhChmNm7cCMDXX39N165dzbZfuXIlr0PKEaVKlQIwm4YzpaCgoGyd28vLi8GDBzN48GAALl26xIABAzhy5Ajjxo1j9erVAPj5+QHwyiuvMHbs2Gw9Vna5uLjw9ttv8/bbbwNw+/ZtRo4cya+//sqIESOMXbAy8ttvvzFy5EicnJzYtm2bcbalpyU/12bNmrF48eKceRJCCCEKBBmDIEQxkzxvf2o//M6fP8+ZM2fyOKKckdy3/88//yQyMtJs+x9//JFqeXZUq1aNjz/+GMCkvl566SUANm3alKUWmORuSlqtNkfiA8MP+E8//dQsxvScOHGCN954A5VKxaZNm9KdnjT5uW7dujVb3ceEEEIUXJIgCFHMJA8aXrJkicm6ACEhIfTt2zdHf6TmpZYtW1K7dm1iYmIYOXIkSUlJxm3BwcGMGTMmy+fcvXs327dvR6PRmJQrisLvv/8OmCZar7zyCg0aNOD48eP0798/1b73kZGRLF++3KSeS5cuDRhab55+rIycPn2aDRs28OjRI7Ntv/32m1mMabl+/Tovv/wy8fHxrFixghdffDHd/V944QVee+01bt++Tffu3VNtuYmLi+OHH34wDnwXQghROEgXIyGKmQkTJvDnn3+ycuVK9uzZQ926dYmOjmbfvn1UqFCBbt268csvv+R3mFmmUqlYu3Yt/v7+/PDDD+zdu5dmzZoRHx/Pnj17qFOnDk2aNOHIkSMmA4vTc/bsWT744AOcnZ2pW7cuvr6+PHr0iFOnThEUFISLiwvTpk0z7q9Wq9myZQudO3dm9erV/PTTT9SuXZsyZcqQlJTE9evXOXfuHDqdjoCAAONMR2XKlKF+/fr8/fff1KxZk/r162Nra4u7uzuzZ89ON8agoCDeeOMN7OzsqFu3Ln5+fmi1Ws6dO8fly5extrY2mZ40LTNnzuT+/ft4eHiwb9++NLskffHFF8YF3FatWsXDhw/5448/qFq1KrVr16Z8+fIoisLNmzf5559/SEpK4uLFi3h5eWWqzoUQQuQ/SRCEKGYaNWrE33//zaRJkzhx4gRbt27Fz8+PkSNHMmnSJEaOHJnfIWZbjRo1OHnyJJMnT2bHjh1s2bIFPz8/3n//fSZNmkSNGjWAzK9Q3KVLF6Kiojhw4ABXrlzh6NGj2NnZ4efnx7hx4xg+fLjx6n8yX19fjh49SmBgIBs2bODs2bMcP36cEiVK4OvryzvvvEPXrl2Ng8KTbd68mfHjx7Nnzx42bNiAVqulbNmyGSYIjRs3Zvbs2ezfv5+LFy9y+vRpLC0tKV26NMOHD2fkyJFUrVo1w+eq0+kAw4xDyWMqUjN16lRj/Tk5ObFz5042bNjA2rVrOXnyJGfOnMHZ2RkfHx/69OlD165dMxywLYQQomBRKVmdqkQIIQqhGzduUKlSJZycnIiIiMiVWZeEEEKIokC+IYUQRUZcXBznz583Kw8KCqJPnz7o9Xr69esnyYEQQgiRDmlBEEIUGTdv3qR8+fJUrFiRKlWq4OzszK1btzh16hSJiYnUrl2b/fv3GxdaE0IIIYQ5SRCEEEVGbGwsn376Kbt37+bWrVs8fPgQe3t7qlatymuvvcbIkSOxt7fP7zCFEEKIAk0SBCGEEEIIIYSRdMQVQgghhBBCGEmCIIQQQgghhDCSBEEIIYQQQghhJAmCEEIIIYQQwkgSBCGEEEIIIYSRJAhCCCGEEEIII0kQhBBCCCGEEEaSIAghhBBCCCGM/g+G/4PYeP/8PQAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Calculate means and standard deviations\n", "mean_accuracies_Q = [np.mean(sublist) for sublist in accuracies_Q]\n", "std_Q = np.array([np.std(sublist) for sublist in accuracies_Q])\n", "\n", "mean_accuracies_C = [np.mean(sublist) for sublist in accuracies_C]\n", "std_C = np.array([np.std(sublist) for sublist in accuracies_C])\n", "\n", "mean_accuracies_U = [np.mean(sublist) for sublist in accuracies_U]\n", "std_U = np.array([np.std(sublist) for sublist in accuracies_U])\n", "\n", "mean_accuracies_G = [np.mean(sublist) for sublist in accuracies_G]\n", "std_G = np.array([np.std(sublist) for sublist in accuracies_G])\n", "\n", "# Plotting \n", "plt.errorbar(data_sizes, mean_accuracies_Q, \n", " yerr=(std_Q/2, std_Q/2), \n", " fmt='-o', \n", " capsize=5, \n", " capthick=2, \n", " ecolor='tab:blue', \n", " label='Quantum',\n", " )\n", "plt.errorbar(data_sizes, mean_accuracies_C, \n", " yerr=(std_C/2, std_C/2), \n", " fmt='-o', \n", " capsize=5, \n", " capthick=2, \n", " ecolor='tab:orange', \n", " label='Coherent',\n", " )\n", "plt.errorbar(data_sizes, mean_accuracies_U, \n", " yerr=(std_U/2, std_U/2), \n", " fmt='-o', \n", " capsize=5, \n", " capthick=2, \n", " ecolor='tab:green', \n", " label='Unbunching',\n", " )\n", "plt.errorbar(data_sizes, mean_accuracies_G, \n", " yerr=(std_G/2, std_G/2), \n", " fmt='-o', \n", " capsize=5, \n", " capthick=2, \n", " ecolor='tab:red', \n", " label='Gaussian',\n", " )\n", "plt.plot([0, N], [0.5, 0.5], c='black', linestyle='dashed', linewidth=2)\n", "plt.xlabel('Training set size', fontsize=16)\n", "plt.ylabel('Test set accuracy', fontsize=16)\n", "plt.xticks(fontsize=13)\n", "plt.yticks(fontsize=13)\n", "plt.xlim(18, N - 8)\n", "plt.grid(True)\n", "plt.legend(bbox_to_anchor=[1, 1], fontsize=14)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## References\n", "\n", "[1] Z. Yin, I. Agresti, G. de Felice, D. Brown, A. Toumi, C. Pentangelo, S. Piacentini, A. Crespi, F. Ceccarelli, R. Osellame, B. Coecke, and P. Walther, \"Experimental quantum-enhanced kernels on a photonic processor,\" *arXiv*, 2024. [Online]. Available: https://arxiv.org/abs/2407.20364\n", "\n", "[2] S. Aaronson and A. Arkhipov, ‘The Computational Complexity of Linear Optics’, Theory of Computing. vol. 9: pp. 143–252, 2013." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 2 } ================================================ FILE: docs/source/notebooks/requirements.txt ================================================ jupyter nbconvert scikit-learn>=1.4 ================================================ FILE: docs/source/reference/algorithm/analyzer.rst ================================================ Analyzer ^^^^^^^^ The ``Analyzer`` algorithm aims at testing a processor, computing a probability table between input states and expected outputs, a performance score and an error rate. For example, we call the Naive backend that we store in simulator_backend: >>> simulator_backend = pcvl.BackendFactory().get_backend('Naive') We can create an input state that will enter our optical scheme later on. We store it in `input_state` and use `BasicState` from the Perceval library. >>> input_state = pcvl.BasicState("|1,1>") let's simulate the distribution obtained when we input two photons in a beam-splitter. We will use the Naive backend already stored in simulator_backend. We will simulate the behavior of the circuit using the `Circuit Analyzer` which has three arguments: - The first one is an instance of a processor containing the circuit to analyse. - The second one is the input state (we will use `input_state`). - The third one is the desired output states. To compute all possible output states, one just input `"*"`. >>> p = Processor("SLOS", comp.BS()) # create a processor running on SLOS backend >>> ca = pcvl.algorithm.Analyzer(p, [input_state], "*") Then, we display the result of `Analyzer` via :ref:`pdisplay`. >>> pcvl.pdisplay(ca) .. figure:: ../../_static/img/CircuitAnalyzerHOM.png :align: center :width: 40% .. autoclass:: perceval.algorithm.analyzer.Analyzer :members: ================================================ FILE: docs/source/reference/algorithm/index.rst ================================================ algorithm ^^^^^^^^^ The :code:`perceval.algorithm` package contains the code of several simple and generic **quantum algorithms**. It provides a :ref:`Processor`-centric syntax to run an algorithm locally or remotely, on a simulator or an actual QPU. All algorithms take either a local or a remote processor as parameter, in order to perform a task. :ref:`Processor` runs simulations on a local computer while a :ref:`RemoteProcessor` turns Perceval into the client of a remote service such as `Quandela Cloud `_, and the computation is performed remotely, on the selected platform. However, for user experience, an algorithm has the same behavior be it run locally or remotely: every call to an algorithm command returns a :ref:`Job` object, hiding this complexity. >>> from perceval import Processor, BasicState >>> from perceval.algorithm import Sampler >>> local_p = Processor("CliffordClifford2017", pcvl.BS()) >>> local_p.with_input(BasicState('|1,1>')) >>> sampler = Sampler(local_p) >>> local_sample_job = sampler.sample_count Here, the computation has not started yet, but it's been prepared in :code:`local_sample_job` to run locally. **Samples of interest vs Shots** On a QPU, the acquisition is measured in **shots**. A shot is any coincidence with at least 1 detected photon. Shots act as credits on the Cloud services. Users have to set a maximum shots value they are willing to use for any given task. >>> remote_p = pcvl.RemoteProcessor("sim:sampling") >>> remote_p.set_circuit(pcvl.BS()) >>> remote_p.with_input(pcvl.BasicState('|1,1>')) >>> sampler = pcvl.algorithm.Sampler(remote_p, max_shots_per_call=500_000) >>> remote_sample_job = sampler.sample_count Here, the computation was set-up to run on `sim:sampling` platform when :code:`remote_sample_job` is executed. For more information about the shots and shots/samples ratio estimate, please read :ref:`Remote computing tutorial`. .. toctree:: sampler analyzer tomography ================================================ FILE: docs/source/reference/algorithm/sampler.rst ================================================ Sampler ^^^^^^^ The :code:`Sampler` is the simplest algorithm provided, yet an important gateway to using processors. All processors do not share the same capabilities. For instance, a QPU is able to sample, but not to sample output probabilities given an input. The :code:`Sampler` allows users to call any of the three main `primitives` on any processor: >>> sampler = pcvl.algorithm.Sampler(processor) >>> samples = sampler.samples(10000) # Sampler exposes 'samples' primitive returning a list of ordered samples >>> print(samples['results']) [|0,1,0,1,0,0>, |0,1,0,0,1,0>, |0,2,0,0,0,0>, |0,0,0,1,0,0>, |0,1,0,1,0,0>, |0,1,0,1,0,0>, |0,1,1,0,0,0>, |0,1,0,1,0,0>, |0,1,1,0,0,0>, |0,1,0,1,0,0>, ... (size=10000)] >>> sample_count = sampler.sample_count(10000) # Sampler exposes 'sample_count' returning a dictionary {state: count} >>> prob_dist = sampler.probs() # Sampler exposes 'probs' returning a probability distribution of all possible output states When a `primitive` is not available on a processor, a :ref:`conversion` occurs automatically after the computation is complete. Batch jobs ++++++++++ The :code:`Sampler` can setup a batch of different sampling tasks within a single job. Such a job enables you to gain some time (overhead of job management) as well as tidy up your job list, especially when running on the Quandela Cloud (but it can still be used in a local simulation context). The system relies on defining a circuit containing variable parameters, then with each iteration of the batch job, you can set values for: * The circuit `variable parameters` - each iteration must define a value for all variable parameters so that the circuit is fully defined, * the `input state`, * the `detected photons filter`. >>> c = BS() // PS(phi=pcvl.P("my_phase")) // BS() # Define a circuit containing "my_phase" variable >>> processor = pcvl.RemoteProcessor("qpu:altair", token_qcloud) >>> processor.set_circuit(c) >>> sampler = Sampler(processor) >>> sampler.add_iteration(circuit_params={"my_phase": 0.1}, >>> input_state=BasicState([1, 1]), >>> min_detected_photons=1) # You can add a single iteration >>> sampler.add_iteration_list([ >>> {"circuit_params": {"my_phase": i/2}, >>> "input_state": BasicState([1, 1]), >>> "min_detected_photons": 1 >>> } for i in range(1, 6) >>> ]) # Or you can add multiple iterations at once >>> sample_count = sampler.sample_count(10000) .. note:: As the same input state is used for all iterations, it could have been set once with :code:`processor.with_input` method and :code:`input_state` removed from every iteration definition. This job will iterate over all the sampling parameters in a batch and return all the results at once. >>> results_list = sample_count["results_list"] # Note that all the results are stored in the "results_list" field >>> for r in results_list: >>> print(r["iteration"]['circuit_params']) # Iteration params are available along with the other result fields >>> print(r["results"]) {'my_phase': 0.1} { |1,0>: 3735 |0,1>: 3828 |1,1>: 2437 } {'my_phase': 0.5} { |1,0>: 4103 |0,1>: 3972 |1,1>: 1925 } {'my_phase': 1.0} { |1,0>: 4650 |0,1>: 4607 |1,1>: 743 } {'my_phase': 1.5} { |1,0>: 5028 |0,1>: 4959 |1,1>: 13 } {'my_phase': 2.0} { |1,0>: 4760 |0,1>: 4788 |1,1>: 452 } {'my_phase': 2.5} { |1,0>: 4155 |0,1>: 4252 |1,1>: 1593 } Sampler code reference ++++++++++++++++++++++ .. autoclass:: perceval.algorithm.sampler.Sampler :members: ================================================ FILE: docs/source/reference/algorithm/tomography.rst ================================================ Tomography ^^^^^^^^^^ The tomography module in Perceval provides tools for performing quantum tomography experiments. StateTomography *************** .. autoclass:: perceval.algorithm.tomography.tomography.StateTomography :members: ProcessTomography ***************** .. autoclass:: perceval.algorithm.tomography.tomography.ProcessTomography :members: TomographyMLE ^^^^^^^^^^^^^ .. autoclass:: perceval.algorithm.tomography.tomography_mle.TomographyMLE :members: StateTomographyMLE ****************** .. autoclass:: perceval.algorithm.tomography.tomography_mle.StateTomographyMLE :members: ProcessTomographyMLE ******************** .. autoclass:: perceval.algorithm.tomography.tomography_mle.ProcessTomographyMLE :members: ================================================ FILE: docs/source/reference/backends/clifford2017.rst ================================================ Clifford2017Backend ^^^^^^^^^^^^^^^^^^^ The :code:`Clifford2017Backend` is a sampling backend that is able to compute random output states according to the exact output distribution without computing it, by computing sub-permanents of chosen matrices and computing sampling weights from them, adding the photons one by one. The algorithm is introduced in :cite:t:`clifford_classical_2018`. This backend has the advantage of being able to handle more modes and photons than the strong simulation backends, and does not need to represent the whole output space, so it is much more memory efficient, at the cost of only being able to approximate the resulting distribution. This backend is available in :ref:`Processor` by using the name :code:`"CliffordClifford2017"`. >>> import perceval as pcvl >>> c = pcvl.BS() >>> backend = pcvl.Clifford2017Backend() >>> backend.set_circuit(c) >>> backend.set_input_state(pcvl.BasicState([1, 0])) >>> print(backend.samples(10)) # Results may vary [ |1,0>, |0,1>, |0,1>, |0,1>, |1,0>, |0,1>, |1,0>, |0,1>, |0,1>, |0,1> ] .. autoclass:: perceval.backends._clifford2017.Clifford2017Backend :members: :inherited-members: ================================================ FILE: docs/source/reference/backends/index.rst ================================================ backends ^^^^^^^^ Backends are the lowest level of computation of perceval. They all aim at providing ways of simulating a :ref:`Circuit` or unitary for a non-annotated :ref:`FockState`. As such, they are only suited to non-noisy, linear, non-polarized, non-superposed input and output states and circuits (unless the user wants to deal with such by hand). We distinguish two kinds of backends: - The strong simulation backends, that can compute probability amplitudes for given output states or the whole distribution. - The sampling backends, that can provide a random output state following the output distribution without computing it. The strong simulation backends are all capable of computing single output probability (or amplitude), the whole distribution as a list or a :ref:`BSDistribution`, evolve a state, and use masks to reduce the computation space to be faster. Note that the output of the strong simulation backends is not normalized, so the sum of the values is not always guaranteed to be 1 for approximating backends or when there is a mask. On the other hand, the sampling backends are only able to sample randomly output states. A comparison of the backends is available at :ref:`Simulation Back-ends`. .. toctree:: :maxdepth: 2 slos slap clifford2017 naive naive_approx mps ================================================ FILE: docs/source/reference/backends/mps.rst ================================================ MPSBackend ^^^^^^^^^^ The :code:`MPSBackend` is a strong simulation backend that can compute the whole output distribution at once by using higher dimensional matrices and evolving them through the components of a circuit. It is efficient to compute the whole output distribution of a circuit. However, this backend has several major downsides: - It is not able to process components spanning more than two modes (except :ref:`Permutation` components). - It needs a cutoff number that avoids growing the matrices exponentially but leads to approximate results. - It's time complexity scales in the number of components, and not only the number of modes and photons. - Although masks work as expected, this backend is not able to make profit of the reduced computation space. Thus, using it is recommended only for small circuits with small components where the whole distribution is needed but the exact value of the probabilities is not needed. In any other case, other backends like :ref:`SLOSBackend` or :ref:`NaiveBackend` are more suited. This backend is available in :ref:`Processor` by using the name :code:`"MPS"`. Unlike other backends, this backend needs a cutoff number that will induce imprecision on the results. Higher values give more accurate results at the cost of a heavier computation. In principle, a high enough value will give exact results, but the computation will be heavier than with a :ref:`SLOSBackend` for example. >>> import perceval as pcvl >>> c = pcvl.Circuit(4) // pcvl.BS() // (2, pcvl.BS()) // (1, pcvl.BS()) >>> backend = pcvl.MPSBackend(cutoff=3) >>> backend.set_circuit(c) >>> backend.set_input_state(pcvl.BasicState([1, 0, 1, 0])) >>> print(backend.prob_distribution()) { |1,1,0,0>: 0.12500000000000003 |1,0,1,0>: 0.07775105849101832 |1,0,0,1>: 0.29017090063073997 |0,2,0,0>: 0.12500000000000003 |0,1,1,0>: 0.020833333333333356 |0,1,0,1>: 0.07775105849101838 |0,0,2,0>: 0.12500000000000006 |0,0,1,1>: 0.12500000000000003 } .. autoclass:: perceval.backends._mps.MPSBackend :members: :inherited-members: ================================================ FILE: docs/source/reference/backends/naive.rst ================================================ NaiveBackend ^^^^^^^^^^^^ The :code:`NaiveBackend` is a strong simulation backend that can compute a single output probability amplitude at a time, by computing the permanent of a :math:`n \times n` matrix, with a time complexity of :math:`\mathrm{O}(n2^n)` (see :cite:t:`ryser1963combinatorial` and :cite:t:`glynn2010permanent`). As such, it is very efficient to compute very precise output states, but not to compute the whole distribution. Thus, using it is not recommended in :ref:`Simulator` (except when using :meth:`probability()`) or :ref:`Processor`, but it is well suited for applications where only a few output probabilities matter. If the whole or most of the computational space is needed, other backends like :ref:`SLOSBackend` are more suited. This backend is available in :ref:`Processor` by using the name :code:`"Naive"`. >>> import perceval as pcvl >>> c = pcvl.Circuit(4) // pcvl.BS() // (2, pcvl.BS()) >>> backend = pcvl.NaiveBackend() >>> backend.set_circuit(c) >>> backend.set_input_state(pcvl.BasicState([1, 0, 1, 0])) >>> print(backend.prob_amplitude(pcvl.BasicState([1, 0, 0, 1]))) 0.5j .. autoclass:: perceval.backends._naive.NaiveBackend :members: :inherited-members: ================================================ FILE: docs/source/reference/backends/naive_approx.rst ================================================ NaiveApproxBackend ^^^^^^^^^^^^^^^^^^ Like the :ref:`NaiveBackend`, the :code:`NaiveApproxBackend` is a strong simulation backend that can compute a single output probability amplitude at a time, by computing the permanent of a :math:`n \times n` matrix. However, instead of computing exactly the permanent, the :code:`NaiveApproxBackend` uses the Gurvit estimate algorithm to approximate it with a 99% confidence interval (see :cite:t:`gurvits2002`). It is very efficient to compute very precise output states, but not to compute the whole distribution, and it can be used with more modes and photons than the :ref:`NaiveBackend` at the cost of losing precision on the result. Thus, using it is not recommended in :ref:`Simulator` (except when using :meth:`probability()`) or :ref:`Processor`, but it is well suited for applications where only a few output probabilities matter with many photons. If the whole or most of the computational space is needed, other backends like :ref:`SLOSBackend` are more suited. This backend is available in :ref:`Processor` by using the name :code:`"NaiveApprox"`. Unlike other backends, this backend needs a number of iterations to use to estimate the permanent. Also, in addition to the generic backend methods, this backend offers means to get a 99% confidence interval on the probability or a 99% sure error bound on the amplitude. >>> import perceval as pcvl >>> circuit_size = 60 >>> n_photons = 30 >>> backend = pcvl.NaiveApproxBackend(100_000_000) # Number of iterations; higher values reduce the error bound >>> backend.set_circuit(pcvl.Unitary(pcvl.Matrix.random_unitary(circuit_size))) >>> input_state = pcvl.BasicState([1]*n_photons + [0]*(circuit_size-n_photons)) >>> backend.set_input_state(input_state) >>> interval = backend.probability_confidence_interval(BasicState([1]*n_photons + [0]*(circuit_size-n_photons))) >>> print(f"Probability in {interval}") Probability in [6.051670221391749e-20, 1.5297683283662674e-19] .. autoclass:: perceval.backends._naive_approx.NaiveApproxBackend :members: :inherited-members: ================================================ FILE: docs/source/reference/backends/slap.rst ================================================ SLAPBackend ^^^^^^^^^^^ The :code:`SLAPBackend` (for Simulator of LAttice of Polynomials) is a strong simulation backend that, like the :code:`SLOSBackend`, is able to compute efficiently all the output probabilities at once. It achieves its goal by computing partial derivatives of a polynomial along a graph. The algorithm is introduced in :cite:t:`goubault2025`. The main advantage compared to SLOS is that this graph is not represented in memory, so this backend is more memory efficient, with the downside of being slower when memory is not a limitation (for instance with relatively few photons :math:`n < 10`). If only a few output states are needed, other backends like :ref:`NaiveBackend` are more suited. This backend is available in :ref:`Processor` by using the name :code:`"SLAP"`. >>> import perceval as pcvl >>> c = pcvl.Unitary(pcvl.Matrix.random_unitary(4)) >>> backend = pcvl.SLAPBackend() >>> backend.set_circuit(c) >>> backend.set_input_state(pcvl.BasicState([1, 0, 1, 0])) >>> print(backend.all_prob()) # Results are random due to random unitary [0.22615963112684112, 0.059932460984674245, 0.11409780074515555, 0.05869159993147518, 0.06610964358209905, 0.1384083292588432, 0.1266841040718083, 0.08819140959446393, 0.05777776134512867, 0.0639472593595116] .. autoclass:: perceval.backends._slap.SLAPBackend :members: :inherited-members: ================================================ FILE: docs/source/reference/backends/slos.rst ================================================ SLOSBackend ^^^^^^^^^^^ The :code:`SLOSBackend` (for Strong Linear Optical Simulation) is a strong simulation backend that is able to compute efficiently the entire output distribution by representing in memory a calculation path in which photons are added one by one, with the best time complexity among strong simulation backends: :math:`\mathrm{O}(nC_n^{n+m-1})`. It is introduced in :cite:t:`heurtel2022`. The major downside of this backend is the memory intensive consumption, with the same complexity of :math:`\mathrm{O}(nC_n^{n+m-1})`. This backend is able to use masks to reduce the computation space, making it cheaper in memory and faster. As such, this backend is well suited with a relatively small number of photons and modes (:math:`n, m < 20`) when it is necessary to compute everything (or at least everything that befalls into a mask). If only a few output states are needed, other backends like :ref:`NaiveBackend` are more suited. This backend is available in :ref:`Processor` by using the name :code:`"SLOS"`. >>> import perceval as pcvl >>> c = pcvl.Circuit(4) // pcvl.BS() // (2, pcvl.BS()) >>> backend = pcvl.SLOSBackend() >>> backend.set_circuit(c) >>> backend.set_input_state(pcvl.BasicState([1, 0, 1, 0])) >>> print(backend.prob_distribution()) { |1,0,1,0>: 0.2500000000000001 |1,0,0,1>: 0.2500000000000001 |0,1,1,0>: 0.2500000000000001 |0,1,0,1>: 0.2500000000000001 } .. autoclass:: perceval.backends._slos.SLOSBackend :members: :inherited-members: ================================================ FILE: docs/source/reference/components/catalog.rst ================================================ Catalog ======= The concept of the *catalog* is to provide the user a set of basic of qbit or photonic gate or circuit. All circuits share the same interface that is describe by the following class ``CatalogItem``: .. autoclass:: perceval.components.component_catalog.CatalogItem :members: The catalog object work as a dictionary. To select the wanted component you must address it with its catalog key. For example to get an heralded CZ gate, you must call it as followed: .. code-block:: Python from perceval import catalog catalog['heralded cz'] You can after either get it as a circuit or a processor: .. code-block:: Python from perceval import catalog processor = catalog['heralded cz'].build_processor() # Will return a processor circuit = catalog['heralded cz'].build_circuit() # Will return a circuit If a gate have parameters, like for instance a Mach-Zehnder interferometer phase first you can set those parameters as followed: .. code-block:: Python import math from perceval import catalog circuit = catalog["mzi phase first"].build_circuit(phi_a=math.pi, phi_b=math.pi)) .. include:: ../../../build/catalog.rst ================================================ FILE: docs/source/reference/components/circuit.rst ================================================ Circuit ======= The basic usage and definition of Circuit can be found in :ref:`the tutorial`. Accessing the components ^^^^^^^^^^^^^^^^^^^^^^^^ Circuits can be iterated over to retrieve their components. Each component is a tuple :code:`(r, c)` where :code:`r` is a tuple of integers corresponding to the modes of the component, in ascending order, and :code:`c` is the component instance itself. >>> import perceval as pcvl >>> circuit = pcvl.Circuit(3) // pcvl.BS() // (1, pcvl.PS(1)) // (1, pcvl.BS()) >>> for r, c in circuit: >>> print(r, c.name) (0, 1) BS.Rx (1,) PS (1, 2) BS.Rx .. note:: The iterator on a :code:`Circuit` flattens the circuit structure, so only basic components will be returned when using a :code:`for` loop on a circuit. It is also possible to access directly a component from a circuit using `row` and `column` indices - note that a same component may have different column indices for the different rows it spans over: >>> c = Circuit(2) // comp.BS.H() // comp.PS(P("phi1")) // comp.BS.Rx() // comp.PS(P("phi2")) >>> print(c[1, 1].describe()) BS(convention=BSConvention.Rx) >>> print(c[0, 2].describe()) BS(convention=BSConvention.Rx) Circuit and parameters ^^^^^^^^^^^^^^^^^^^^^^ For circuits or components using symbolic parameters (see :ref:`Parameter`), some convenient ways to access them exist. Note that two parameters in the same circuit can't have the same name. If a parameter's name appears in more than one component, it can only be the same :code:`Parameter` instance. Probably the most useful of them is :meth:`assign()`, as it allows setting the value for all variable parameters of a circuit at once, even without having to store them somewhere. >>> p = pcvl.P("phi0") >>> c = pcvl.PS(p) >>> c.assign({"phi0": 2.53}) >>> print(float(p)) 2.53 .. note:: The :code:`assign` argument of :meth:`compute_unitary()` does exactly that if you want to compute the unitary with values. Beware however that this has the side-effect of changing the values of the parameters even outside :meth:`compute_unitary()`. You can remove the values for the variable parameters to get back sympy expressions using the circuit's :meth:`reset_parameters()` method. The names of the parameters can be obtained using the :meth:`params` property. Note that this includes the fixed parameters if there are any. To get the :code:`Parameter` itself, there are three ways: - use the :meth:`param("param name")` method to retrieve a single parameter from its name. - use the :meth:`get_parameters()` method that gives all the parameters defined by the arguments (variable or all, with or without expressions). This is the preferred method for getting all parameters. >>> c = BS(theta=pcvl.P("alpha1")) // PS(pcvl.P("phi")) // BS(theta=pcvl.P("alpha2")) >>> for params in c.get_parameters(): >>> print(param) Parameter(name='alpha1', value=None, min_v=0.0, max_v=12.566370614359172) Parameter(name='phi', value=None, min_v=0.0, max_v=6.283185307179586) Parameter(name='alpha2', value=None, min_v=0.0, max_v=12.566370614359172) - use the :meth:`vars` property to get a dictionary mapping the name of the variable parameters and their instances. .. autoclass:: perceval.components.linear_circuit.Circuit :members: :inherited-members: :special-members: __ifloordiv__, __floordiv__, __matmul__, __imatmul__, __iter__ ================================================ FILE: docs/source/reference/components/detector.rst ================================================ Detector ======== Detectors are components that aim at simulating real hardware detectors, or perfect detectors. They all share a common interface for your own usage. >>> import perceval as pcvl >>> d = pcvl.Detector.threshold() >>> d.detect(3) # Common method for all kind of detectors |1> Detectors can also be added to :ref:`Processor` or :ref:`Experiment` mode by mode for an automatic usage. Once a detector is added to a :code:`Processor` or an :code:`Experiment` mode, this mode is considered to be a classical mode, so optical components can no longer be added to it, but classical components can now be added to it. >>> import perceval as pcvl >>> e = pcvl.Experiment(2) >>> e.add(0, pcvl.BS()) # Can add optical component >>> e.add(0, pcvl.Detector.ppnr(12)) >>> # e.add(0, pcvl.PS(1)) # Can no longer add optical component >>> ff_config = pcvl.FFCircuitProvider(1, 0, pcvl.PS(3.14)).add_configuration((1,), pcvl.PS(1.57)) >>> e.add(0, ff_config) # Can add classical component .. autoclass:: perceval.components.detector.Detector :members: :inherited-members: :exclude-members: is_composite .. autoclass:: perceval.components.detector.BSLayeredPPNR :members: :inherited-members: :exclude-members: is_composite .. autoenum:: perceval.components.detector.DetectionType .. autofunction:: perceval.components.detector.get_detection_type ================================================ FILE: docs/source/reference/components/experiment.rst ================================================ Experiment ^^^^^^^^^^ Experiments behave the same way as Processors and RemoteProcessors, except that they don't know how or where to simulate them (they don't have backend or platform); they simply describe the elements of an optical table and the post-processing rules. >>> import perceval as pcvl >>> e = pcvl.Experiment(2, noise=pcvl.NoiseModel(0.8), name="my experiment").add(0, pcvl.BS()) >>> e.add_herald(0, 1) >>> p = pcvl.Processor("SLOS", e) >>> rp = pcvl.RemoteProcessor("sim:slos").add(e) Experiments have two main purposes that :code:`Processor` and :code:`RemoteProcessor` can't fulfill: - They can be used to create several Processors describing the same experiment with different backends. - They can be serialized using perceval serialization, so they can be stored and retrieved easily. >>> from perceval.serialization import serialize, deserialize >>> e_str = serialize(e) # This is a regular string >>> e_copy = deserialize(e_str) .. autoclass:: perceval.components.experiment.Experiment :members: ================================================ FILE: docs/source/reference/components/feed_forward_configurator.rst ================================================ Feed-forward Configurators ========================== Configurators are the way to perform a feed-forward computation. As they are non-unitary components, they can only be added to :code:`Processor` instances. Their purpose is to link measurements on given modes to circuits to configure. They are based on the following common features: * A default circuit is mandatory for when the measurement does not fall into one of the configured cases. This circuit determines the size of the configured circuit. * All referenced circuits have the same size. * The measured modes must be classical (placed after a detector). * The circuit position is determined by an offset between the configurator and the configured circuit. This offset represents the number of modes between the measured modes and the circuit. For positive offsets, the circuit is placed below, the offset being the number of empty modes between the configurator and the circuit (0 means the circuit is right below the configurator). For negative values, the circuit is placed above the measured modes (-1 means that the circuit is right above the configurator). Two configurators exist. FFCircuitProvider ----------------- This class directly links measurements to circuits or processors. Any circuit or processor matching the default circuit size can be used given all parameters have numerical values. >>> import perceval as pcvl >>> p = pcvl.Processor("SLOS", 4) >>> c = pcvl.FFCircuitProvider(1, offset=1, default_circuit=pcvl.Circuit(2), name="FFCircuitProvider Example") >>> c.add_configuration([1], pcvl.BS()) >>> p.add(0, pcvl.Detector.threshold()) >>> p.add(0, c) .. autoclass:: perceval.components.feed_forward_configurator.FFCircuitProvider :members: :inherited-members: FFConfigurator -------------- This class links measurements to a mapping of parameter values that can be set in the given circuit. >>> import perceval as pcvl >>> p = pcvl.Processor("SLOS", 4) >>> phi = pcvl.P("phi") >>> c = pcvl.FFConfigurator(2, offset=1, controlled_circuit=pcvl.PS(phi), default_config={"phi": 0}, name="FFConfigurator Example") >>> c.add_configuration([1, 0], {"phi": 1.23}) >>> p.add(0, pcvl.Detector.threshold()) >>> p.add(1, pcvl.Detector.threshold()) >>> p.add(0, c) .. autoclass:: perceval.components.feed_forward_configurator.FFConfigurator :members: :inherited-members: ================================================ FILE: docs/source/reference/components/generic_interferometer.rst ================================================ Generic Interferometer ^^^^^^^^^^^^^^^^^^^^^^ GenericInterferometer usage example =================================== It is possible to define a generic interferometer with the class :class:`perceval.components.GenericInterferometer`. For instance, the following defines a triangular interferometer on 8 modes using a beam splitter and a phase shifter as base components: >>> c = pcvl.GenericInterferometer(8, ... lambda i: comp.BS() // comp.PS(pcvl.P("φ%d" % i)), ... shape=pcvl.InterferometerShape.TRIANGLE) >>> pcvl.pdisplay(c) .. figure:: ../../_static/img/generic-interferometer.png :align: center :width: 75% GenericInterferometer code reference ==================================== .. autoenum:: perceval.utils._enums.InterferometerShape .. autoclass:: perceval.components.generic_interferometer.GenericInterferometer :members: ================================================ FILE: docs/source/reference/components/index.rst ================================================ components ^^^^^^^^^^ .. toctree:: :maxdepth: 2 unitary_components circuit generic_interferometer experiment processor source non_unitary_components port detector feed_forward_configurator catalog ================================================ FILE: docs/source/reference/components/non_unitary_components.rst ================================================ Non-unitary Components ^^^^^^^^^^^^^^^^^^^^^^ Supported non-unitary components ================================ .. list-table:: :header-rows: 1 :width: 100% * - Name - Class Name - ``PhysSkin`` display style - ``SymbSkin`` display style * - :ref:`Time Delay` - ``TD`` - .. image:: ../../_static/library/phys/dt.png - .. image:: ../../_static/library/symb/dt.png * - :ref:`Loss Channel` - ``LC`` - .. image:: ../../_static/library/phys/lc.png - .. image:: ../../_static/library/symb/lc.png Time Delay ========== .. autoclass:: perceval.components.non_unitary_components.TD :members: :inherited-members: :exclude-members: identify, is_composite, transfer_from Loss Channel ============ .. autoclass:: perceval.components.non_unitary_components.LC :members: :inherited-members: :exclude-members: identify, is_composite, transfer_from ================================================ FILE: docs/source/reference/components/port.rst ================================================ Ports and data encoding ^^^^^^^^^^^^^^^^^^^^^^^ Ports are a mean to describe data encoding at the input and the output of a circuit in an :ref:`Experiment`. Encoding ======== .. autoenum:: perceval.utils._enums.Encoding :members: PortLocation ============= .. autoenum:: perceval.components.port.PortLocation :members: Port ==== .. autoclass:: perceval.components.port.Port :members: Herald ====== .. autoclass:: perceval.components.port.Herald :members: Utilitary functions =================== .. autofunction:: perceval.components.port.get_basic_state_from_ports .. autofunction:: perceval.components.port.get_basic_state_from_encoding ================================================ FILE: docs/source/reference/components/processor.rst ================================================ Processor ========= Processor is a mean to run a quantum algorithm locally (i.e. on the user's computer) using a simulation back-end. It contains a linear optics :ref:`Experiment` which can be defined in several ways: >>> import perceval as pcvl >>> p = pcvl.Processor("SLOS", 4, name="my proc") # Creates a 4-modes Processor named "my proc" that will be simulated using SLOS >>> p.m 4 A processor can be created empty with a given number of modes, or using a circuit, or an :code:`Experiment`. >>> p = pcvl.Processor("SLOS", pcvl.BS()) # Creates a 2-modes Processor with a single beam splitter as component Processor composition --------------------- Components, circuits and experiments can be added to processors using the :meth:`add` method (note however that :code:`//` doesn't work for processors). When another :code:`Processor` is added, only the enclosed :ref:`Experiment` is copied which means that the right handside back-end is omitted, and only the one from the left processor is kept. >>> p.add(0, pcvl.PS(3.14)) # Add a phase shifter on mode 0 However, unlike :ref:`Circuit`, non-linear components can also be added to Processors >>> p.add(1, pcvl.TD(1)) # Adds a time-delay on mode 1 Secondly, the mode on which a component is added has a few more options than just an integer. One can use a list or a dict of integers to map the output of the current processor to the input of the added component. If the left processor has output ports and the right processor has input ports, it can also be a dict describing the port names. This adds up a permutation before inserting the new component, and its inverse at the end (so modes don't move when doing this). Note however that when adding a processor with asymmetrical heralds (see below), the inverse permutation is not added since it doesn't exist, so modes might move (check with a :code:`pdisplay`). >>> p.add([1, 0], pcvl.BS(theta=0.7)) # Left mode 1 will connect to right mode 0, and left mode 0 will connect to right mode 1 >>> p.add({1: 0, 0: 1}, pcvl.BS(theta=0.7)) # Same as above Composition is a powerful tool to achieve complex processors: .. figure:: ../../_static/img/complex-processor.png :align: center A processor composed of a Hadamard gate and two heralded CNOT gates. Detectors can also be added to a Processor using the same syntax >>> p.add(0, pcvl.Detector.threshold()) Once a :code:`Detector` has been added, no optical component can be added anymore on this mode. Setting an input state ---------------------- Before a Processor can be simulated, an input state must be provided. >>> p.with_input(pcvl.BasicState([1, 0])) The input state can be: - A :code:`BasicState`, in which case the noise from the noise model is computed. - A :code:`LogicalState` if ports have been defined, in which case the noise is computed. - A :code:`StateVector` - A :code:`SVDistribution` If a :code:`BasicState` has polarization, the method to use is :code:`p.with_polarized_input`, and no noise from the source will be applied. Noise model ----------- Processors can be given noise model to apply noise both on the source and on the components This noise model can be given at instantiation >>> p = pcvl.Processor("SLOS", 4, pcvl.NoiseModel(brightness=0.9, phase_error=0.01)) or changed later during the life of a processor >>> p.noise = pcvl.NoiseModel(brightness=0.8, g2=0.03) Min photons filter ------------------ A threshold on the number of detected photons can be set so outputs having less than this number of photons are filtered out. This has an impact on the perfs of the Processor. >>> p.min_detected_photons_filter(3) # Outputs will all have at least 3 photons Ports ----- Once a Processor has been defined in terms of components, one can add ports and heralds to it. If a port spans over several modes, the specified mode is considered to be the upper one. >>> p.add_port(0, pcvl.Port(pcvl.Encoding.DUAL_RAIL, "qubit0")) # Adds a dual rail port on modes 0 and 1 on both sides >>> p.remove_port(0) >>> p.add_port(0, pcvl.Port(pcvl.Encoding.DUAL_RAIL, "qubit0"), location=pcvl.PortLocation.INPUT) # Add the port on the left of the processor Ports have three main purposes: - Showing the circuit's logic in display - Composing processors using ports - Setting an input state >>> p.with_input(pcvl.LogicalState([0])) # Equivalent to BasicState([1, 0]) for a dual rail. Adapts automatically to the ports Heralds ------- Heralds are a special kind of ports that act as modes that the user "doesn't want to see". Note that ports and heralds are mutually exclusive mode-wise. At the input, they declare a number of photon in a mode that the user won't have to specify when using :code:`with_input`. >>> p = pcvl.Processor("SLOS", pcvl.BS()) >>> p.add_herald(0, 1, location=pcvl.PortLocation.INPUT) # Add an herald of value 1 on input mode 0 >>> p.with_input(pcvl.BasicState([1])) # Only one mode >>> p.m_in 1 >>> p.heralds_in {0: 1} At the output, they will automatically filter states so only states matching the given number of photons will be selected. They also remove these modes from the resulting BasicStates. This filtering has an impact on the perf of the processor. >>> p = pcvl.Processor("SLOS", pcvl.BS()) >>> p.add_herald(0, 1, location=pcvl.PortLocation.OUTPUT) # Output will have only one mode >>> p.m 1 >>> p.circuit_size # Real size of the circuit 2 >>> p.heralds {0: 1} Heralded output modes can still be seen using :code:`p.keep_heralds(True)`. In this case, heralded modes can still be removed afterward using :code:`state = p.remove_heralded_modes(state)` Heralds at output are independent from the min detected photons filter, as the filter looks only at non-heralded modes. >>> p.min_detected_photons_filter(2) >>> p.add_herald(0, 1) # There will actually be at least 3 photons A :code:`Processor` that has at least one mode that defines an herald only at input or output is considered asymmetrical. By default, heralds are added on both sides, so Processors are kept symmetrical. When composing processors, the processors are considered to have :code:`m` output modes and :code:`m_in` input modes. Heralds are considered to be outside the processors. Thus, they can be moved to new modes to keep a good structure. Most 2-qubit gates from the catalog are symmetrical processors that use heralds. When composing with a symmetrical processor, the inverse permutation is added at the right to keep the order of the modes. This is not the case when composing with an asymmetric processor. >>> from perceval import catalog >>> p = pcvl.Processor("SLOS", 4) >>> cnot = catalog["postprocessed cnot"].build_processor() >>> cnot.m 4 >>> cnot.circuit_size 6 >>> p.add(0, cnot) # Works despite the cnot having 6 modes >>> p.circuit_size # p is now bigger due to the added heralds from cnot 6 >>> p.heralds {4: 0, 5: 0} PostSelect ---------- A post-selection method can be added to a Processor to filter only states matching it. >>> p.set_postselection(pcvl.PostSelect("[0, 1] == 2")) >>> p.post_select_fn [0, 1] == 2 When composing, the modes are swapped to match the new modes of the composition. Also, it is not allowed to add something to an experiment that has a post-selection if the modes overlap one of the nodes of the post-selection (they should be entirely included or disjoint) If the user knows what they are doing, they can remove the post-selection using :code:`p.clear_postselection()` then apply it again. .. note:: Processor and :ref:`RemoteProcessor` expose the same behaviour in many ways, and most of the time, when a Processor is needed, it can be replaced with a RemoteProcessor, making the processor to write switch from local to remote computation back and forth as easy as possible. Computation ^^^^^^^^^^^ Depending on the backend that was specified at the beginning, a Processor can perform probability computation or sampling. >>> p = pcvl.Processor("SLOS", 4) >>> p.available_commands ["probs"] >>> p = pcvl.Processor("CliffordClifford2017", 3) >>> p.available_commands ["samples"] Any of the available methods can be used to compute the results for this processor, taking into account the components, the input state, the noise, the heralds, the post-selection... In any case, the results is a dict containing the results in the field "results" and some performance score corresponding to the probability of getting a selected state. >>> p = pcvl.Processor("SLOS", pcvl.BS()) >>> p.with_input(pcvl.BasicState([1, 0])) >>> p.probs()["results"] BSDistribution(float, {|1,0>: 0.5, |0,1>: 0.5}) .. autoclass:: perceval.components.processor.Processor :members: :inherited-members: ================================================ FILE: docs/source/reference/components/source.rst ================================================ Source ====== >>> import perceval as pcvl >>> source = pcvl.Source(emission_probability=0.3, multiphoton_component=0.05) >>> pcvl.pdisplay(source.probability_distribution()) +-----------------+-----------------+ | state | probability | +-----------------+-----------------+ | \|0> | 7/10 | +-----------------+-----------------+ | \|{_:0}> | 0.297716 | +-----------------+-----------------+ | \|{_:0}{_:2}> | 0.002284 | +-----------------+-----------------+ .. autoclass:: perceval.components.source.Source :members: :inherited-members: ================================================ FILE: docs/source/reference/components/unitary_components.rst ================================================ Unitary Components ^^^^^^^^^^^^^^^^^^ Overview ======== .. list-table:: :header-rows: 1 :width: 100% * - Name - Class Name - ``PhysSkin`` display style - ``SymbSkin`` display style - Unitary Matrix * - :ref:`Beam Splitter` - ``BS`` - .. image:: ../../_static/library/phys/bs.png - .. image:: ../../_static/library/symb/bs.png - Depends on the convention, see :ref:`Beam Splitter` * - :ref:`Phase Shifter` - ``PS`` - .. image:: ../../_static/library/phys/ps.png - .. image:: ../../_static/library/symb/ps.png - :math:`\left[\begin{matrix}e^{i \phi}\end{matrix}\right]` * - :ref:`Permutation` - ``PERM`` - .. image:: ../../_static/library/phys/perm.png - .. image:: ../../_static/library/symb/perm.png - Example of a two mode permutation: :math:`\left[\begin{matrix}0 & 1\\1 & 0\end{matrix}\right]` * - :ref:`Wave Plate` - ``WP`` - .. image:: ../../_static/library/phys/wp.png - .. image:: ../../_static/library/symb/wp.png - :math:`\left[\begin{matrix}i \sin{\left(\delta \right)} \cos{\left(2 \xi \right)} + \cos{\left(\delta \right)} & i \sin{\left(\delta \right)} \sin{\left(2 \xi \right)}\\i \sin{\left(\delta \right)} \sin{\left(2 \xi \right)} & - i \sin{\left(\delta \right)} \cos{\left(2 \xi \right)} + \cos{\left(\delta \right)}\end{matrix}\right]` * - :ref:`Polarising Beam Splitter` - ``PBS`` - .. image:: ../../_static/library/phys/pbs.png - .. image:: ../../_static/library/symb/pbs.png - :math:`\left[\begin{matrix}0 & 0 & 1 & 0\\0 & 1 & 0 & 0\\1 & 0 & 0 & 0\\0 & 0 & 0 & 1\end{matrix}\right]` * - :ref:`Polarisation Rotator` - ``PR`` - .. image:: ../../_static/library/phys/pr.png - .. image:: ../../_static/library/symb/pr.png - :math:`\left[\begin{matrix}\cos{\left(\delta \right)} & \sin{\left(\delta \right)}\\- \sin{\left(\delta \right)} & \cos{\left(\delta \right)}\end{matrix}\right]` Beam Splitter ============= Beam Splitter conventions ------------------------- .. autoenum:: perceval.components.unitary_components.BSConvention :members: Three specialised conventions are defined, with a single :math:`\theta` parameter, as follow: .. list-table:: :header-rows: 1 :width: 100% * - Convention - Unitary matrix - Default value (:math:`\theta=\pi/2`) - Comment * - ``Rx`` - :math:`\left[\begin{matrix}\cos{(\theta/2)} & i \sin{(\theta/2)}\\i \sin{(\theta/2)} & \cos{(\theta/2)}\end{matrix}\right]` - :math:`\left[\begin{matrix}1 & i\\i & 1\end{matrix}\right]` - Symmetrical, default convention * - ``Ry`` - :math:`\left[\begin{matrix}\cos{(\theta/2)} & -\sin{(\theta/2)}\\ \sin{(\theta/2)} & \cos{(\theta/2)}\end{matrix}\right]` - :math:`\left[\begin{matrix}1 & -1\\1 & 1\end{matrix}\right]` - Real, non symmetrical * - ``H`` - :math:`\left[\begin{matrix}\cos{(\theta/2)} & \sin{(\theta/2)}\\ \sin{(\theta/2)} & -\cos{(\theta/2)}\end{matrix}\right]` - :math:`\left[\begin{matrix}1 & 1\\1 & -1\end{matrix}\right]` - Hadamard gate, ``HH=I``, non symmetrical Each convention also accepts up to four additional phases, mimicking a phase shifter on each mode connected to the beam splitter. For instance, with the ``Rx`` convention, the unitary matrix is defined by: :math:`\left[\begin{matrix}e^{i(\phi_{tl}+\phi_{tr})} \cos{\left(\theta/2 \right)} & i e^{i (\phi_{tr}+\phi_{bl})} \sin{\left(\theta/2 \right)}\\i e^{i \left(\phi_{tl} + \phi_{br}\right)} \sin{\left(\theta/2 \right)} & e^{i (\phi_{br}+\phi_{bl})} \cos{\left(\theta/2 \right)}\end{matrix}\right]` It is thus parametrized by :math:`\theta`, :math:`\phi_{tl}`, :math:`\phi_{bl}`, :math:`\phi_{tr}` and :math:`\phi_{br}` angles, making this beam splitter equivalent to: .. image:: ../../_static/img/bs_rx_4_phases.png Beam Splitter reflectivity -------------------------- :math:`\theta` relates to the reflectivity and :math:`\phi` angles correspond to relative phases between modes. Beam splitters exist as bulk, fibered and on-chip components. The relationship between the reflectivity :math:`R` and :math:`\theta` is: :math:`cos {\left( \theta/2 \right)} = \sqrt{R}`. To create a beam splitter object with a given reflectivity value: >>> from perceval.components import BS >>> R = 0.45 >>> beam_splitter = BS(BS.r_to_theta(R)) Beam Splitter code reference ---------------------------- .. autoclass:: perceval.components.unitary_components.BS :members: :inherited-members: :exclude-members: identify, is_composite, transfer_from Phase Shifter ============= .. autoclass:: perceval.components.unitary_components.PS :members: Permutation =========== A permutation swaps multiple consecutive spatial modes. To create a permutation ``PERM`` sending :math:`\ket{0,1}` to :math:`\ket{1,0}` and vice-versa: >>> from perceval.components import PERM >>> permutation = PERM([1, 0]) More generally, one can define a permutation on an arbitrary number of modes. The permutation is described by a list of integers from 0 to :math:`l-1`, where :math:`l` is the length of the list. The :math:`k^{th}` spatial input mode is sent to the spatial output mode corresponding to the :math:`k` th value in the list. For instance the following defines a 4-mode permutation. It matches the first input path (index 0) with the third output path (value 2), the second input path with the fourth output path, the third input path, with the first output path, and the fourth input path with the second output path. >>> import perceval as pcvl >>> import perceval.components.unitary_components as comp >>> c = comp.PERM([2, 3, 1, 0]) >>> pcvl.pdisplay(c) >>> pcvl.pdisplay(c.compute_unitary(), output_format=pcvl.Format.LATEX) .. list-table:: * - .. image:: ../../_static/library/phys/perm-2310.png :width: 180 - .. math:: \left[\begin{matrix}0 & 0 & 0 & 1\\0 & 0 & 1 & 0\\1 & 0 & 0 & 0\\0 & 1 & 0 & 0\end{matrix}\right] .. autoclass:: perceval.components.unitary_components.PERM :members: Unitary ======= .. autoclass:: perceval.components.unitary_components.Unitary :members: Wave Plate ========== .. autoclass:: perceval.components.unitary_components.WP :members: .. autoclass:: perceval.components.unitary_components.HWP :members: .. autoclass:: perceval.components.unitary_components.QWP :members: Polarisation Rotator ==================== .. autoclass:: perceval.components.unitary_components.PR :members: Polarising Beam Splitter ======================== .. autoclass:: perceval.components.unitary_components.PBS :members: Barrier ======= .. autoclass:: perceval.components.unitary_components.Barrier :members: ================================================ FILE: docs/source/reference/error_mitigation.rst ================================================ error_mitigation ================ The error_mitigation package provides tools and algorithms for mitigating errors in quantum systems. Loss Mitigation *************** Photon recycling was introduced in the following publication: :cite:t:`mills2024` .. autofunction:: perceval.error_mitigation.loss_mitigation.photon_recycling ================================================ FILE: docs/source/reference/exqalibur/circuit_optimizer.rst ================================================ CircuitOptimizer ================ This class allows for low level calls which are wrapped in a Python class having the same name and a simpler API (see :ref:`Circuit Optimizer`). .. autoclass:: exqalibur.CircuitOptimizer :members: ================================================ FILE: docs/source/reference/exqalibur/clifford2017.rst ================================================ Clifford2017 ============ .. autoclass:: exqalibur.Clifford2017 :members: ================================================ FILE: docs/source/reference/exqalibur/config.rst ================================================ Config ====== .. autoclass:: exqalibur.Config .. autoattribute:: exqalibur.Config.compute_max_thread_count .. autoattribute:: exqalibur.Config.use_single_precision .. autoattribute:: exqalibur.Config.unitary_check_precision set_seed ======== .. autofunction:: exqalibur.set_seed ================================================ FILE: docs/source/reference/exqalibur/fockstate.rst ================================================ Fock state classes ^^^^^^^^^^^^^^^^^^ FockState --------- .. autoclass:: exqalibur.FockState :members: NoisyFockState -------------- .. autoclass:: exqalibur.NoisyFockState :members: AnnotatedFockState ------------------ .. autoclass:: exqalibur.Annotation :members: .. autoclass:: exqalibur.AnnotatedFockState :members: ================================================ FILE: docs/source/reference/exqalibur/fs_utils.rst ================================================ FSMask ====== .. autoclass:: exqalibur.FSMask :members: FSArray ======= .. autoclass:: exqalibur.FSArray :members: ================================================ FILE: docs/source/reference/exqalibur/index.rst ================================================ exqalibur ^^^^^^^^^ **exqalibur** is a C++ toolbox of highly optimised algorithms. **Perceval** relies heavily on it. .. toctree:: permanent fockstate fs_utils state_data_structure statevector config postselect slos slap clifford2017 source circuit_optimizer ================================================ FILE: docs/source/reference/exqalibur/permanent.rst ================================================ Permanent ========= Exact permanent computation --------------------------- .. autofunction:: exqalibur.permanent_fl .. autofunction:: exqalibur.permanent_cx Permanent estimate computation ------------------------------ .. autofunction:: exqalibur.estimate_permanent_fl .. autofunction:: exqalibur.estimate_permanent_cx ================================================ FILE: docs/source/reference/exqalibur/postselect.rst ================================================ PostSelect ========== .. autoclass:: perceval.utils.postselect.PostSelect :members: :special-members: __call__ .. autofunction:: perceval.utils.postselect.post_select_distribution .. autofunction:: perceval.utils.postselect.post_select_statevector ================================================ FILE: docs/source/reference/exqalibur/slap.rst ================================================ SLAP ==== `SLAP` (Simulator of LAttice of Polynomials) is a strong simulation back-end. The exqalibur C++ class is highly optimised and wrapped in a Python class exposing the same API as other strong simulation back-ends (see :ref:`SLAPBackend`) .. autoclass:: exqalibur.SLAP :members: ================================================ FILE: docs/source/reference/exqalibur/slos.rst ================================================ all_prob_normalize_output ========================= .. autofunction:: exqalibur.all_prob_normalize_output ================================================ FILE: docs/source/reference/exqalibur/source.rst ================================================ Source ====== .. autoclass:: exqalibur.Source :members: SourceSampler ============= .. autoclass:: exqalibur.SourceSampler :members: ================================================ FILE: docs/source/reference/exqalibur/state_data_structure.rst ================================================ BSDistribution ============== .. autoclass:: exqalibur.BSDistribution :members: BSCount ======= .. autoclass:: exqalibur.BSCount :members: BSSamples ========= .. autoclass:: exqalibur.BSSamples :members: ================================================ FILE: docs/source/reference/exqalibur/statevector.rst ================================================ StateVector =========== The ``StateVector`` class is the mean to create superposed pure states in the Fock space. State vector arithmetics ^^^^^^^^^^^^^^^^^^^^^^^^ A :code:`StateVector` can be built using arithmetic. While only applying arithmetic operations to a state vector, no automatic normalization is called, allowing the composition of state vectors through multiple Python statements. >>> from exqalibur import StateVector >>> sv = StateVector("|1>") + StateVector("|2>") >>> sv += StateVector("|3>") >>> print(sv) # All components of sv have the same amplitude 0.577*|1>+0.577*|2>+0.577*|3> :code:`StateVector` can be built with great freedom: >>> import math >>> from exqalibur import FockState, StateVector >>> sv = 0.5j * FockState([1, 1]) - math.sqrt(2) * StateVector("|2,0>") + StateVector([0, 2]) * 0.45 >>> print(sv) 0.319I*|1,1>-0.903*|2,0>+0.287*|0,2> .. warning:: When multiplying a state by a numpy scalar (such as one returned by a numpy function), numpy takes precedence over the state arithmetics and tries to convert the state to a numpy array. This results in an exception with potentially obscure message. Two solutions exist: putting the numpy number on the right of the :code:`*` operand, or converting the numpy scalar to a python type using the :code:`.item()` method. * **Comparison operators** Comparing two :code:`StateVector` with operator :code:`==` or :code:`!=` compare normalised copies of each. probability amplitudes are compared strictly (they have to be exactly the same to be considered equal). .. note:: ``StateVector`` will normalize themselves only at usage (iteration, sampling, measurement), and not during state arithmetics operations. ``StateVector`` can also be multiplied with a tensor product: >>> import exqalibur as xq >>> sv0 = xq.StateVector([1,0]) + xq.StateVector([0,1]) >>> sv1 = 1j*xq.StateVector([2]) - xq.StateVector([0]) >>> bs = xq.FockState([0]) >>> print(sv0 * sv1 * bs) 0.5I*|0,1,2,0>-0.5*|0,1,0,0>+0.5I*|1,0,2,0>-0.5*|1,0,0,0> Exponentiation is also built-in: >>> print(sv1 ** 3) # equivalent to sv1 * sv1 * sv1 -0.354I*|2,2,2>+0.354I*|2,0,0>+0.354*|2,2,0>+0.354*|2,0,2>+0.354I*|0,2,0>+0.354*|0,2,2>-0.354*|0,0,0>+0.354I*|0,0,2> StateVector code reference ^^^^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: exqalibur.StateVector :members: SVDistribution ============== .. autoclass:: exqalibur.SVDistribution :members: ================================================ FILE: docs/source/reference/providers.rst ================================================ providers ========= Quandela -------- Quandela is Perceval default Cloud provider. If no session is created, Quandela Cloud endpoints will be used. When using Quandela Cloud, you have the same capabilities with and without using a session. It's a matter of code style. .. autoclass:: perceval.providers.quandela.quandela_session.Session :members: Scaleway -------- `Scaleway Quantum-as-a-Service `_ provides access to allocate and program Quantum Processing Units (QPUs), physical or emulated. Scaleway authentication ^^^^^^^^^^^^^^^^^^^^^^^ To use Scaleway QaaS as a provider you need a Scaleway account, a Scaleway Project ID and an API key. 1. `Create a Scaleway account `_ 2. `Create a Scaleway Project `_ 3. `Create a Scaleway API key `_ ScalewaySession ^^^^^^^^^^^^^^^ .. autoclass:: perceval.providers.scaleway.Session :members: Allocate a QPU session ^^^^^^^^^^^^^^^^^^^^^^ Let's see step by step how to instantiate and use a `Scaleway` session. Import the library and Scaleway from the providers library: >>> import perceval as pcvl >>> import perceval.providers.scaleway as scw Provide your Scaleway Project ID and API key: >>> PROJECT_ID = "your-scaleway-project-id" >>> TOKEN = "your-scaleway-api-key" Choose one of the Perceval compatible platforms `provided by Scaleway `_: >>> PLATFORM_NAME = "EMU-SAMPLING-L4" # For emulated QPU >>> # PLATFORM_NAME = "QPU-BELENOS-12PQ" # For real QPU You can now create a Scaleway session: >>> session = scw.Session(platform_name=PLATFORM_NAME, project_id=PROJECT_ID, token=TOKEN) >>> session.start() >>> /* ... * Session scope ... */ >>> session.stop() You can also create a Scaleway session using a ``with`` block: >>> with scw.Session(platform_name=PLATFORM_NAME, project_id=PROJECT_ID, token=TOKEN) as session: ... # ... # Session scope ... # Note: using a ``with`` block you do not need to start and stop your session: it starts automatically at the beginning of the block and stops automatically at its end. .. note:: while using a Jupyter Notebook for convenience python objects are kept alive and we recommend using directly ``start`` and ``stop`` methods. Using an existing Scaleway QPU session ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you created your session from the `Scaleway console `_, you can retrieve it from Perceval. For this, you only have to go to your session's settings on the console, copy the deduplication identifier and put it to the session creation on your Perceval code. >>> DEDUPLICATION_ID = "my-quantum-workshop-identifier" >>> session = scw.Session(platform=PLATFORM_NAME, project_id=PROJECT_ID, token=TOKEN, deduplication_id=DEDUPLICATION_ID) A session can be fetched until termination or timeout. If there is no alive session matching the deduplication_id, a new one will be created and returned. It is highly convenient if you wish to keep a specific amount of session alive at a time. Send a circuit to a Scaleway QPU session ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Now you are handling a session, you can instantiate a :code:`RemoteProcessor` linked to the session: >>> processor = session.build_remote_processor() Then, we can attach a toy circuit and send it on our session >>> processor.set_circuit(pcvl.Circuit(m=2, name="a-toy-circuit") // pcvl.BS.H()) >>> processor.with_input(pcvl.BasicState("|0,1>")) >>> processor.min_detected_photons_filter(1) >>> sampler = pcvl.algorithm.Sampler(processor, max_shots_per_call=10_000) >>> job = sampler.samples(100) >>> print(job) Congratulation you can now design and send jobs to Scaleway QaaS through your processor. You can continue with the documentation of :ref:`algorithm`. ================================================ FILE: docs/source/reference/rendering/display_config.rst ================================================ DisplayConfig ============= This class allows you to choose a display skin for pdisplay and save the chosen skin across perceval runs. .. code-block:: from perceval.rendering import DisplayConfig, SymbSkin DisplayConfig.select_skin(SymbSkin) # SymbSkin will be used by default by pdisplay if no other skin is defined. DisplayConfig.save() # Will save the current DisplayConfig into your Perceval persistent configuration. .. autoclass:: perceval.rendering.DisplayConfig :members: :inherited-members: ================================================ FILE: docs/source/reference/rendering/index.rst ================================================ rendering ^^^^^^^^^ .. toctree:: pdisplay skins display_config ================================================ FILE: docs/source/reference/rendering/pdisplay.rst ================================================ pdisplay ======== Also known as "Perceval pretty display", is a generic function designed to display a lot of different types of Perceval objects. This method has two variants: - :code:`pdisplay` that displays the object immediately. The format and place of output might depend on where your code is executed (IDE, notebook...) and the kind of object that is displayed. - :code:`pdisplay_to_file` that doesn't display the object but saves its representation in a file specified by a string path. This method always uses :code:`Format.MPLOT` (for matplotlib) by default, so you might need to specify it by hand it for some objects. Display a circuit ^^^^^^^^^^^^^^^^^ Any circuit coded in perceval can be displayed. You just need to make the code associated with the desired circuit, let's call it circ, and add pcvl.pdisplay(circ) afterwards in the python cell. Note that components follow the same rules than circuits for displaying. Let's do an example to understand: you want to display the Mach-Zendher Interferometer. Start by doing the code associated to the circuit. .. code-block:: import perceval.components.unitary_components as comp mzi = (pcvl.Circuit(m=2, name="mzi") .add((0, 1), comp.BS()) .add(0, comp.PS(pcvl.Parameter("phi1"))) .add((0, 1), comp.BS()) .add(0, comp.PS(pcvl.Parameter("phi2")))) Then, add ``pcvl.pdisplay()`` of your circuit. .. code-block:: pcvl.pdisplay(mzi) .. figure:: ../../_static/img/mzi.png :align: center :width: 75% .. tip:: The outcome of this last command will depend on your environment. .. list-table:: :header-rows: 1 :width: 100% * - Text Console - Jupyter Notebook - IDE (Pycharm, Spyder, etc) * - .. image:: ../../_static/img/terminal-screenshot.jpg - .. image:: ../../_static/img/jupyter-screenshot.jpg - .. image:: ../../_static/img/ide-screenshot.jpg Controlling the circuit rendering --------------------------------- Also, you can change the display of the circuit using a different skin which can itself be configured. Indeed, a boolean can be set to obtain a more compact display (if the circuit is too wide for example). .. code-block:: import perceval as pcvl import perceval.components.unitary_components as comp from perceval.rendering.circuit import SymbSkin C = pcvl.Circuit.decomposition(pcvl.Matrix(comp.PERM([3, 1, 0, 2]).U), comp.BS(R=pcvl.P("R")), phase_shifter_fn=comp.PS) symbolic_skin = SymbSkin(compact_display=True) pcvl.pdisplay(C, skin=symbolic_skin) .. figure:: ../../_static/img/decomposition_symb_compact.png :align: center :width: 40% .. code-block:: symbolic_skin = SymbSkin(compact_display=False) pcvl.pdisplay(C, skin=symbolic_skin) .. figure:: ../../_static/img/decomposition_symb_compact_false.png :align: center :width: 50% By default the skin will be ``PhysSkin``, if you want to use another skin by default, you can save your configuration into your Perceval persistent configuration. Currently, the other skin provided with `Perceval` is ``SymbSkin``: .. figure:: ../../_static/img/comparing-symb-and-phys.png :align: center :width: 75% To save the skin in your configuration, you need to use the :ref:`DisplayConfig` object. The possible kwargs when displaying a :code:`Circuit` are - :code:`output_format`. The format to use for the output, from the :code:`Perceval.Format` enum. The available formats are MPLOT (default), TEXT and LATEX. - :code:`recursive`. If :code:`True`, the first layer of inner circuits will be exposed in the rendering. - :code:`compact`. If no skin is provided, the skin taken from the :code:`DisplayConfig` will have this value for :code:`compact_display` - :code:`precision`. The numerical precision to display. - :code:`nsimplify`. If :code:`True` (default), some values will be displayed with known mathematical values (pi, sqrt, fractions) if close enough - :code:`skin`. The skin to use. If none is provided, the skin from the :code:`DisplayConfig` will be used. Display a Processor ^^^^^^^^^^^^^^^^^^^ Like circuits, :code:`Processor` can also be displayed using :code:`pdisplay`. Note that the behaviour is strictly the same for :code:`Experiment` and :code:`RemoteProcessor`. For a :code:`Processor` containing only a circuit, the result is the same as for displaying a :code:`Circuit`. However, some objects defined in the :code:`Processor` will also be displayed if defined, modifying the look of the results. Every object that can be named will display its name near to where it is shown. We take the same example as before for this demonstration. Ports ----- When defined, ports are represented by encapsulating the mode numbers in rectangles. >>> proc = pcvl.Processor("SLOS", mzi) >>> proc.add_port(0, pcvl.Port(pcvl.Encoding.DUAL_RAIL, "two modes"), location=pcvl.PortLocation.INPUT) >>> proc.add_port(0, pcvl.Port(pcvl.Encoding.RAW, "one mode"), location=pcvl.PortLocation.OUTPUT) >>> pcvl.pdisplay(proc, recursive=True) .. figure:: ../../_static/img/proc_with_ports.png :align: center :width: 75% Detectors --------- When defined, detectors are displayed at the right of the circuit with different shapes to be more recognizable: - PNR detectors are represented by a half-circle - PPNR detectors are represented by a polygon - threshold detectors are represented by a triangle >>> proc = pcvl.Processor("SLOS", 3).add(0, mzi) >>> proc.add(0, pcvl.Detector.pnr()) >>> proc.add(1, pcvl.Detector.ppnr(24)) >>> proc.add(2, pcvl.Detector.threshold()) >>> pcvl.pdisplay(proc, recursive=True) .. figure:: ../../_static/img/proc_with_detectors.png :align: center :width: 75% Notice that in that case, the mode number is shown as if there was a single-mode port. Also, if there are components after detectors (such as feed-forward configurators), the color of the optical path will change to indicate that this mode is now a classical mode. Heralds ------- When defined, heralds are displayed using half-circles with their value inside the circle. Also, they hide a part of the optical path to be as close as possible to the components they are used in. The modes numbers on which the heralds are defined are not shown. >>> proc = pcvl.Processor("SLOS", mzi) >>> proc.with_input(pcvl.BasicState([1, 0])) >>> pcvl.pdisplay(proc, recursive=True) .. figure:: ../../_static/img/proc_with_herald.png :align: center :width: 75% In case a detector is defined on the same mode than an herald, the half-circle is replaced by the shape of the detector. Notice that for PNR detectors, the shape doesn't change, but the number of expected photons is added into the half-circle. Input state ----------- If possible, the photons of the input state will be displayed at the left of the processor. This is globally the case when the input state is a :code:`BasicState`, and the processor is displayed in a SVG format. >>> proc = pcvl.Processor("SLOS", mzi) >>> proc.with_input(pcvl.BasicState([1, 0])) >>> pcvl.pdisplay(proc, recursive=True) .. figure:: ../../_static/img/proc_with_input.png :align: center :width: 75% Notice that in this case, the modes are displayed as if they had a single-mode port (if no port is defined), and the photons are displayed like heralds except that they don't hide the mode. Displaying a Matrix ^^^^^^^^^^^^^^^^^^^ Matrices, both numeric and symbolic, can be displayed using :code:`pdisplay` >>> m = Matrix([[1, 2], ["x/2", np.pi]], use_symbolic=True) >>> pcvl.pdisplay(m) ⎡1 2 ⎤ ⎣x/2 pi⎦ The possible kwargs when displaying a :code:`Matrix` are - :code:`output_format`. The format to use for the output, from the :code:`perceval.Format` enum. The available formats are TEXT (default), and LATEX. - :code:`precision`. The numerical precision to display numbers. Using :code:`pdisplay` on a :code:`pcvl.Matrix` is a simple way to include a LaTex rendering in a document. .. figure:: ../../_static/img/mzi_matrix.png :align: center :width: 30% Displaying a DensityMatrix ^^^^^^^^^^^^^^^^^^^^^^^^^^ Density matrices are displayed differently than regular matrices. Instead of a table, :code:`pdisplay` uses matplotlib to represent them using a 2d plot where pixels represent the amplitudes of different states. See :ref:`Density matrices in Fock space` The possible kwargs when displaying a :code:`DensityMatrix` are: - :code:`color`. If :code:`True` (default), the result is a color image where colors represent the phase of the states. - :code:`cmap`. Any colormap from matplotlib as a str to use to represent the phase of the states. Displaying a distribution ^^^^^^^^^^^^^^^^^^^^^^^^^ :code:`StateVector`, :code:`BSCount`, :code:`BSDistribution` and :code:`SVDistribution` can be displayed in a table format using :code:`pdisplay`, the behaviour being similar for all these classes. All of them are normalized before being displayed. >>> state = pcvl.StateVector([1, 0]) + 1j * pcvl.StateVector([0, 1]) >>> pcvl.pdisplay(state) +-------+-------------+ | state | prob. ampl. | +-------+-------------+ | |1,0> | sqrt(2)/2 | | |0,1> | I*sqrt(2)/2 | +-------+-------------+ The main use of this display is that it can sort the keys by value and display only a limited number of keys, allowing the display to be fast and to keep only the most important information. >>> bsd = pcvl.BSDistribution({pcvl.BasicState([0]): 0.1, pcvl.BasicState([1]): 0.7, pcvl.BasicState([2]): 0.2}) >>> pcvl.pdisplay(bsd, max_v=2) # Sort by default, keep only the 2 highest values +-------+-------------+ | state | probability | +-------+-------------+ | |1> | 7/10 | | |2> | 1/5 | +-------+-------------+ The possible kwargs when displaying a :code:`StateVector`, :code:`BSCount`, :code:`BSDistribution` or :code:`SVDistribution` are - :code:`output_format`. The format to use for the output, from the :code:`Perceval.Format` enum. The available formats are TEXT (default), LATEX and HTML. - :code:`nsimplify`. If :code:`True` (default), some values will be displayed with known mathematical values (pi, sqrt, fractions) if close enough. However, if the distribution to be displayed is too large and if this parameter is not manually set to :code:`True`, this numerical simplification will be unactivated. - :code:`precision`. The numerical precision to display numbers. - :code:`max_v`. The number of values to display. - :code:`sort`. If :code:`True` (default), values will be sorted before being displayed. Displaying samples ^^^^^^^^^^^^^^^^^^ :code:`BSSamples` can be displayed in a table format using :code:`pdisplay`. >>> bs_samples = pcvl.BSSamples() >>> bs_samples.append(perceval.BasicState("|1,0>")) >>> bs_samples.append(perceval.BasicState("|1,1>")) >>> bs_samples.append(perceval.BasicState("|0,1>")) >>> pcvl.pdisplay(bs_samples) +--------+ | states | +--------+ | |1,0> | | |1,1> | | |0,1> | +--------+ The number of displayed states can be limited with the parameter :code:`max_v`. >>> pcvl.pdisplay(bs_samples, max_v=2) # keep only the 2 first values +--------+ | states | +--------+ | |1,0> | | |1,1> | +--------+ The possible kwargs when displaying a :code:`BSSamples` are - :code:`output_format`. The format to use for the output, from the :code:`Perceval.Format` enum. The available formats are TEXT (default), LATEX and HTML. - :code:`max_v`. The number of values to display. :code:`max_v` is 10 by default. Displaying algorithms ^^^^^^^^^^^^^^^^^^^^^ Some algorithms can be passed to :code:`pdisplay` to display their results easily. Analyzer -------- In the case of the :code:`Analyzer`, it displays the results as a table, as well as the performance and fidelity of the gate. See usage in :ref:`Ralph CNOT Gate`. The possible kwargs when displaying an :code:`Analyzer` are: - :code:`output_format`. The format to use for the output, from the :code:`Perceval.Format` enum. The available formats are TEXT (default), LATEX and HTML. - :code:`nsimplify`. If :code:`True` (default), some values will be displayed with known mathematical values (pi, sqrt, fractions) if close enough - :code:`precision`. The numerical precision to display numbers. Tomography ---------- In the case of a tomography algorithm, it displays the results as a table, as well as the performance and fidelity of the gate. See usage in :ref:`Tomography of a CNOT Gate`. The possible kwargs when displaying a tomography algorithm are: - :code:`precision`. The numerical precision to display numbers. - :code:`render_size`. The size to create the matplotlib figure. Display a JobGroup ^^^^^^^^^^^^^^^^^^ :code:`pdisplay` can be used to represent a :code:`JobGroup`. The result is a table showing a resume of the status of the jobs inside the job group. >>> jg = pcvl.JobGroup("example") # Result might change depending on what is in this group >>> pcvl.pdisplay(jg) +--------------+-------+--------------------------------------+ | Job Category | Count | Details | +--------------+-------+--------------------------------------+ | Total | 8 | | | Finished | 5 | {'successful': 4, 'unsuccessful': 1} | | Unfinished | 3 | {'sent': 1, 'not sent': 2} | +--------------+-------+--------------------------------------+ The possible kwargs when displaying a tomography algorithm are: - :code:`output_format`. The format to use for the output, from the :code:`Perceval.Format` enum. The available formats are TEXT (default), LATEX and HTML. Display a graph ^^^^^^^^^^^^^^^ :code:`pdisplay` offers support to quickly display graphs from :code:`nx.graph`. The possible kwargs when displaying a graph are: - :code:`output_format`. The format to use for the output, from the :code:`Perceval.Format` enum. The available formats are MPLOT (default), and LATEX. Code reference ^^^^^^^^^^^^^^ .. automodule:: perceval.rendering.pdisplay :members: .. autoenum:: perceval.rendering.format.Format :members: ================================================ FILE: docs/source/reference/rendering/skins.rst ================================================ Circuit rendering skins ^^^^^^^^^^^^^^^^^^^^^^^ When rendering a :ref:`Circuit`, an :ref:`Experiment` or a :ref:`Processor`, you can select a skin which will change how the components are displayed. >>> import perceval as pcvl >>> c = pcvl.Circuit(4).add(0, pcvl.BS.H()).add(2, pcvl.BS.H()) >>> pcvl.pdisplay(c, skin = pcvl.SymbSkin(compact_display = True)) .. figure:: ../../_static/img/symb_skin_example.png :align: center :width: 20% Perceval provides two skins for an easy usage: * :code:`SymbSkin`: a sober black and white skin * :code:`PhysSkin`: a more colorful "real-life" skin (default one) Both share the same init signature, with an optional boolean kwarg :code:`compact_display` that makes the circuit components smaller and closer to each other. Skin classes can be selected to be used across several perceval runs using the :ref:`DisplayConfig`. >>> from perceval.rendering import DisplayConfig, SymbSkin >>> DisplayConfig.select_skin(SymbSkin) # SymbSkin will be used by default by pdisplay if no other skin is defined. >>> DisplayConfig.save() # Will save the current DisplayConfig into your Perceval persistent configuration. Note that since the :ref:`DisplayConfig` class stores a skin class and not an instance, the `compact_display` attribute needs to be given again each time. It's also possible to hack an existing skin to fit your needs or even create a new one by subclassing the :code:`ASkin` abstract class, though it will not be easy to save it. Perceval also provides a :code:`DebugSkin` that builds on :code:`PhysSkin`, but that also displays the heralded modes of an Experiment, and highlights whether phase shifters are defined or not. As its name suggests, this skin should only be used when debugging as it is not made for a pretty and readable display. Skin code reference =================== All skins follow the :code:`ASkin` interface, except for the :meth:`__init__()` where subclasses already have their own style: .. autoclass:: perceval.rendering.circuit.abstract_skin.ASkin :members: ================================================ FILE: docs/source/reference/runtime/index.rst ================================================ runtime ^^^^^^^ .. toctree:: remote_processor remote_config job job_status job_group rpchandler ================================================ FILE: docs/source/reference/runtime/job.rst ================================================ Job ^^^ Job is class responsible for the computation and retrieval of a task's results. It hides the `local` vs `remote`, and `synchronous` vs `asynchronous` executions, which are orthogonal concepts, even if it's more natural for a local job to be run synchronously, and a remote job asynchronously. The local vs remote question is handled by two different classes, :code:`LocalJob` and :code:`RemoteJob`, sharing the same interface. * Execute a job synchronously >>> args = [10_000] # for instance, the expected sample count >>> results = job.execute_sync(*args) # Executes the job synchronously (blocks the execution until results are ready) >>> results = job(*args) # Same as above * Execute a job asynchronously >>> job.execute_async(*args) This call is non-blocking, however results are not available when this line has finished executing. The job object provides information on the progress. >>> while not job.is_complete: # Check if the job has finished running ... print(job.status.progress) # Progress is a float value between 0. and 1. representing a progress from 0 to 100% ... time.sleep(1) >>> if job.is_failed: # Check if the job has failed ... print(job.status.stop_message) # If so, print the reason >>> results = job.get_results() # Retrieve the results if any Typically, the results returned by an algorithm is a Python dictionary containing a ``'results'`` key, plus additional data (performance scores, etc.). * A job cancellation can be requested programmatically by the user >>> job.cancel() # Ask for job cancelation. The actual end of the execution may take some time When a job is canceled, it may contain partial results. To retrieve them, call :meth:`get_results()`. * A remote job can be resumed as following: >>> remote_processor = pcvl.RemoteProcessor("any:platform") >>> job = remote_processor.resume_job("job_id") # You can find job IDs on Quandela Cloud's website >>> print(job.id) # The ID field is also available in every remote job object * At any time, the user can retrieve a :ref:`JobStatus` to retrieve several job metadata: >>> job_status = job.status LocalJob ======== .. autoclass:: perceval.runtime.local_job.LocalJob :inherited-members: name, get_results, __call__ RemoteJob ========= .. autoclass:: perceval.runtime.remote_job.RemoteJob :inherited-members: name, get_results, __call__ ================================================ FILE: docs/source/reference/runtime/job_group.rst ================================================ JobGroup ======== The :code:`JobGroup` class is designed to help manage jobs client-side by storing them in named groups. Large experiments can be easily cut in chunks and even ran during multiple days, over multiple Python sessions, the job group will make sure all data can be retrieved from a single location. .. warning:: JobGroups store their job data in the persistent data directory. As these files can grow quite large, you will have to explicitly erase the ones you don't want to keep. JobGroup provides the following commands: * :code:`JobGroup.delete_job_group(name)` * :code:`JobGroup.delete_job_groups_date(del_before_date: datetime)` * :code:`JobGroup.delete_all_job_groups()` Usage example ------------- Here's an example creating a job group with two jobs, running the same acquisition on a post-processed vs an heralded CNOT gate: >>> import perceval as pcvl >>> from perceval.algorithm import Sampler >>> >>> p_ralph = pcvl.RemoteProcessor("sim:altair") >>> p_ralph.add(0, pcvl.catalog["postprocessed cnot"].build_processor()) >>> p_ralph.min_detected_photons_filter(2) >>> p_ralph.with_input(pcvl.BasicState([0, 1, 0, 1])) >>> sampler_ralph = Sampler(p_ralph, max_shots_per_call=1_000_000) >>> >>> p_knill = pcvl.RemoteProcessor("sim:altair") >>> p_knill.add(0, pcvl.catalog["heralded cnot"].build_processor()) >>> p_knill.min_detected_photons_filter(2) >>> p_knill.with_input(pcvl.BasicState([0, 1, 0, 1])) >>> sampler_knill = Sampler(p_knill, max_shots_per_call=1_000_000) >>> >>> jg = pcvl.JobGroup("compare_knill_and_ralph_cnot") >>> jg.add(sampler_ralph.sample_count, max_samples=10_000) >>> jg.add(sampler_knill.sample_count, max_samples=10_000) This first script only prepared the experiment, nothing was executed remotely. Before going on, it's important for a user to know the details of their plan on the Cloud, for this will establish the number of job they can run concurrently. The job group supports executing jobs sequentially or in parallel and includes the ability to rerun failed jobs, if needed. The second script may be used exclusively to run jobs. It includes a built-in `tqdm` progress bar to provide real-time updates on job execution. To run jobs sequentially with a given delay: >>> import perceval as pcvl >>> >>> jg = pcvl.JobGroup("compare_knill_and_ralph_cnot") # Loads prepared experiment data >>> jg.run_sequential(0) # Will send the 2nd job to the Cloud as soon as the first one is complete Other methods - :code:`jg.run_parallel()`, :code:`jg.rerun_failed_parallel()`, :code:`jg.rerun_failed_sequential(delay)`, :code:`jg.launch_async_jobs()`, and :code:`jg.relaunch_async_failed_jobs()`. .. note:: The :code:`jg.run_parallel()` and `jg.launch_async_jobs()` methods try to start as many jobs as possible according to the pricing plan (see `Quandela Cloud `_). This number of jobs can be limited manually by setting the `concurrent_job_count` parameter of `jg.launch_async_jobs()`. Note that for custom clouds that don't expose a way to retrieve the number of possible concurrent job, this parameter is mandatory. A third script can then prepared to analyze results: >>> import perceval as pcvl >>> >>> jg = pcvl.JobGroup("compare_knill_and_ralph_cnot") >>> results = jg.get_results() >>> ralph_res = results[0] >>> knill_res = results[1] >>> perf_ratio = (ralph_res['global_perf']) / (knill_res['global_perf']) >>> print(f"Ralph CNOT is {perf_ratio} times better than Knill CNOT, but needs a measurement to work") Ralph CNOT is 490.01059 times better than Knill CNOT, but needs a measurement to work .. note:: If the connection token you use in a :code:`JobGroup` expires or gets revoked, said :code:`JobGroup` will not be usable anymore. Stay tuned for further improvements on this feature, fixing this issue. Class reference --------------- .. autoclass:: perceval.runtime.job_group.JobGroup :members: ================================================ FILE: docs/source/reference/runtime/job_status.rst ================================================ JobStatus ^^^^^^^^^ A :ref:`Job` object contains a lot of metadata on top of the computation results a user wants to get. These can be retrieved from the :code:`JobStatus` object every job contains. >>> s = my_job.status # s is a JobStatus instance >>> if s.completed: ... print(f"My job lasted {s.duration} seconds.") My job lasted 37 seconds. .. autoclass:: perceval.runtime.job_status.JobStatus :members: .. autoenum:: perceval.runtime.job_status.RunningStatus :members: ================================================ FILE: docs/source/reference/runtime/remote_config.rst ================================================ RemoteConfig ^^^^^^^^^^^^ .. note:: We recommend you save your token only on a personal computer, not on shared/public ones. .. autoclass:: perceval.runtime.remote_config.RemoteConfig :members: ================================================ FILE: docs/source/reference/runtime/remote_processor.rst ================================================ RemoteProcessor ^^^^^^^^^^^^^^^ :code:`RemoteProcessor` class is the entry point for sending a computation on a remote platform (a simulator or a QPU). `Quandela Cloud `_ is a public cloud service with available QPUs and simulators. An access token on the selected service is required to connect to a remote platform (e.g. an access token to Quandela Cloud with rights is required to follow this tutorial: :ref:`Remote computing`). Once you have created a token suiting your needs (it needs to be given the rights to run jobs on target platforms), you may save it once and for all on your computer by using the :ref:`RemoteConfig`. A token value can also be set to every :code:`RemoteProcessor` object >>> remote_simulator = RemoteProcessor("platform:name", "YOUR_TOKEN") For the rest of this page, let's assume a token is saved in your environment. A given remote platform is only able to support a specific set of commands. For instance, a real QPU is natively able to sample output detections, but not to compute probabilities of output states versus an input state. Hardware constraints might also enforce the coincidence counting type, or the type of detection (threshold detection or photon number resolving). When creating a :code:`RemoteProcessor`, you can query its capabilities >>> remote_simulator = RemoteProcessor("qpu:belenos") >>> print(remote_simulator.available_commands) ['sample_count', 'samples'] This means `qpu:belenos` is only able to natively answer to `sample_count` and `samples` commands (i.e. return the results of a sampling task). Creation -------- :code:`RemoteProcessor` has the same API and fills the same role as a local :ref:`Processor` but are executed remotely by a Cloud platform (a real QPU or an online simulator). RemoteProcessors are created slightly differently than normal Processors. First, they require connexion information to a given Cloud provider: * A token (or API key) being the credentials to authenticate the user remotely. * A URL to the Cloud API * Optionally, a proxy configuration All these information are used to create a :ref:`RPCHandler` which could be passed instead. Also, these info can be saved in your local computer persistent :ref:`RemoteConfig`. In terms of circuit initialisation, here are the specifics: >>> rp = pcvl.RemoteProcessor("sim:slos", token=..., m=3, noise=pcvl.NoiseModel(0.9)) # m is an optional kwarg here If :code:`m` is not specified, it is inferred from the first added component. They can also be created by converting a local Processor, keeping all defined objects (input state, filter, ports...). >>> rp = pcvl.RemoteProcessor.from_local_processor(p, "sim:slos", token=...) From there, all composition rules are the same, and local processors can be added to remote processors. Input state ----------- Only non-polarized BasicState and LogicalState input are accepted for RemoteProcessors. Computation ----------- The only way to compute with a RemoteProcessor is to use it in a Quantum Algorithm. Misceallenous ------------- Some platforms expose specs that must be fulfilled in order for a Job to be able to be completed. These include (but are not limited to) the number of photons, the number of modes, the number of photons per mode... They can be retrieved using the property :code:`rp.specs` or :code:`rp.constraints` The performances of the source can also be retrieved using the property :code:`rp.performance`. The needed resources in terms of samples or shots can be estimated by a RemoteProcessor >>> rp.estimate_required_shots(nb_samples=10000) Note that this uses a partially noisy local simulation, so it can be expensive to compute. .. autoclass:: perceval.runtime.remote_processor.RemoteProcessor :members: ================================================ FILE: docs/source/reference/runtime/rpchandler.rst ================================================ RPCHandler ^^^^^^^^^^ A :code:`RPCHanlder` (RPC stands for `Remote Procedure Call`) is responsible for all the requests to a Cloud that Perceval supports. It sends the authentication info along with the request data, and reacts to the HTTP errors which might occur. .. autoclass:: perceval.runtime.rpc_handler.RPCHandler ================================================ FILE: docs/source/reference/serialization.rst ================================================ serialization ^^^^^^^^^^^^^ Perceval provides a generic way to serialize most objects into strings that can be deserialized later on to get back the original object. >>> import perceval as pcvl >>> from perceval.serialization import serialize, deserialize # Note: this is not directly at perceval's root >>> s = serialize(pcvl.Circuit(3).add(0, pcvl.BS()).add(1, pcvl.BS())) >>> print(s) :PCVL:zip:eJyzCnAO87FydM4sSi7NLLFydfTN9K9wdI7MSg52DsyO9AkNCtWu9DANqMj3cg50hAPP9GwvBM+xEKgWwXPxRFWbZeDpGERdMwHhijWy >>> c = deserialize(s) # Creates a copy of the circuit from the string representation Serialize ========= Most perceval objects can be serialized. This includes (but is not limited to): - Circuit and basic components (PS, BS, ...) - Experiments - BasicState and StateVector - Matrix - Heralds - Ports - BSDistribution, SVDistribution, BSCount, BSSamples - NoiseModel - PostSelect - Detector .. note:: Some python containers (list, dict) are serialized recursively, so the returned value is a container of strings (or a container of containers of ... of strings). This allows a more simple serialization and deserialization later on using the JSON format, but implies that the returned type will depend on the input type. Note however that some objects can't be serialized. This includes (but is not limited to): - Algorithm - Processor and RemoteProcessor (the experiment within them can however be serialized) .. warning:: Non-serializable objects will be silently returned as they are by the :meth:`serialize()` method, which can produce errors for example if trying to save the result later on. The :meth:`serialize()` method has an optional kwarg argument :code:`compress` that can be either a boolean or a list of types on which to apply the compression (which can be useful for containers). The default value of this parameter depends on the object to serialize. If :code:`compress` is True, or for types that match, a compression will be applied to try to reduce the string size. Note that if :code:`compress` is False, some string representations will be human readable (such as BasicState), and the object type will always be specified by a prefix qualifier. Also, the :code:`serialization` module has a :meth:`serialize_to_file()` method that takes a file path and an object. Note that this method will fail if any of the objects is not serializable either by perceval or by JSON. Deserialize =========== The deserialization part of the process is more straightforward than the serialization part as any class that can be serialized can be deserialized. As such, using :meth:`deserialize()` on an object returned by :meth:`serialize()` should always produce a copy of the initial object, or the object itself if it is not serializable. .. note:: Using serialization to make copies inside a single code instance is generally a bad idea as it is expected to be slow compared to a direct copy of the objects. There is also a :meth:`deserialize_file()` method that can deserialize anything that was previously stored using :meth:`serialize_to_file()` using the same file path. ================================================ FILE: docs/source/reference/simulators/ff_simulator.rst ================================================ FFSimulator =========== The :code:`FFSimulator` is a simulator dedicated to simulate feed-forward experiments. Like the :code:`Simulator`, it needs a strong simulation backend to be able to perform simulations. However, the :code:`FFSimulator` is also able to compute circuits having :ref:`FFConfigurator` or :ref:`FFCircuitProvider` but is unable to compute probability amplitudes. Thus, only the :code:`probs_svd` and :code:`probs` computation methods are available. >>> import perceval as pcvl >>> sim = pcvl.FFSimulator(pcvl.SLOSBackend()) >>> ff_not = pcvl.FFCircuitProvider(2, 0, pcvl.Circuit(2)).add_configuration([0, 1], pcvl.PERM([1, 0])) >>> sim.set_circuit([((0, 1), ff_not)]) # Since non-unitary components can't be added to Circuit, we directly provide a list; the number of modes is implicit >>> sim.probs(pcvl.BasicState([0, 1, 1, 0])) { |0,1,0,1>: 1.0 } .. autoclass:: perceval.simulators.feed_forward_simulator.FFSimulator :members: :inherited-members: ================================================ FILE: docs/source/reference/simulators/index.rst ================================================ simulators ^^^^^^^^^^ .. toctree:: simulator simulator_factory noisy_sampling_simulator ff_simulator stepper ================================================ FILE: docs/source/reference/simulators/noisy_sampling_simulator.rst ================================================ NoisySamplingSimulator ====================== The :code:`NoisySamplingSimulator` is a special simulator dedicated to sample states from a noisy input state. It is completely separated from the other simulators, and, as such, it is not available through the :code:`SimulatorFactory`. As its name suggests, it requires a sampling able backend such as :ref:`CliffordClifford2017`. The :code:`NoisySamplingSimulator` exposes two simulation methods: - :code:`samples` that returns a python dictionary with a BSSamples in the "results" field - :code:`sample_count` that returns a python dictionary with a BSCount in the "results" field Note that these two methods require a :code:`SVDistribution` without superposed states (but can be annotated), since we can't retrieve the probability amplitudes from the backend. Also, this simulator can only simulate non-polarized unitary circuits. Using a NoisySamplingSimulator ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: import perceval as pcvl sim = pcvl.NoisySamplingSimulator(pcvl.Clifford2017Backend()) sim.set_circuit(pcvl.BS()) # Now, sim holds a 2 modes circuit # Physical and logical selection sim.set_selection(min_detected_photons_filter = 2) # Other fields: heralds (accounting only the output), postselect sim.set_detectors([pcvl.Detector.threshold(), pcvl.Detector.pnr()]) svd = pcvl.SVDistribution({pcvl.BasicState("|{_:0}, {_:1}>"): 1}) # Sample stream print(sim.samples(svd, 5)["results"]) # Random sampling; results may change at each run # [ |1,1>, |0,2>, |0,2>, |1,1>, |1,1> ] # Sample count print(sim.sample_count(svd, max_samples = 10, max_shots = 10)["results"]) # { # |1,1>: 7 # |0,2>: 2 # } .. autoclass:: perceval.simulators.NoisySamplingSimulator :members: :inherited-members: ================================================ FILE: docs/source/reference/simulators/simulator.rst ================================================ Simulator ========= The :code:`Simulator` class is a mid-level class that can be used to compute output probabilities or amplitudes for photonic experiment. It adds logic that allows computing with objects that are more complex than non-annotated BasicStates, contrarily to the backend it build upon to do its computations. Note that the :code:`Simulator` aims at computing exact probabilities, so it requires a backend able to compute probability amplitudes, such as :ref:`SLOS`, :ref:`SLAP`, :ref:`Naive`... (see :ref:`Simulation Back-ends`) If possible, it will also automatically use masks on the backend to reduce the computation time and memory. Also, contrarily to :ref:`Experiment` or :ref:`Processor`, it relies on being given complete information from the start and cannot be used to do composition, remote computing... The basic :code:`Simulator` is only able to perform computation with non-polarized unitary circuits. For polarized circuits, see the :code:`PolarizationSimulator`. For non-unitary circuits, see the :code:`DelaySimulator`, :code:`LossSimulator`, and :code:`FFSimulator`. More generally, if you are unsure about which simulator to use, see :code:`SimulatorFactory`. Using a Simulator ^^^^^^^^^^^^^^^^^ .. code-block:: import perceval as pcvl sim = pcvl.Simulator(pcvl.SLOSBackend()) sim.set_circuit(pcvl.BS()) # Now, sim holds a 2 modes circuit # Physical and logical selection sim.set_selection(min_detected_photons_filter = 2) # Other fields: heralds (accounting only the output), postselect sim.set_precision(1e-6) # Relative precision; only input states having more than this times the highest input prob will be computed # Computation state by state print(sim.prob_amplitude(pcvl.BasicState("|{_:0}, {_:1}>"), pcvl.BasicState("|{_:0}, {_:1}>"))) # No selection # (0.5000000000000001+0j) # Compute everything print(sim.probs(pcvl.BasicState([2, 1]))) # Computes the BSD with selection; no performance and no automatic usage of masks # { # |1,2>: 0.12500000000000003 # |3,0>: 0.375 # |2,1>: 0.12500000000000003 # |0,3>: 0.375 # } # Compute everything from anything svd = pcvl.SVDistribution({pcvl.StateVector([1, 1]) + 0.5j * pcvl.StateVector([0, 2]): 0.7, pcvl.StateVector([1, 0]): 0.3}) print(sim.probs_svd(svd, [pcvl.Detector.threshold()] * 2)) # Can also simulate detectors # {'results': BSDistribution(, {|1,1>: 1.0}), 'physical_perf': 0.06999999999999998, 'logical_perf': 1.0000000000000004} Computation methods ^^^^^^^^^^^^^^^^^^^ A lot of computation methods exist in the :code:`Simulator` for different usages. Here is a list of the simulation methods in the :code:`Simulator` +----------------------+----------------------------------------------------------+---------------------------+ | **method name** | **parameters** | **output** | +----------------------+----------------------------------------------------------+---------------------------+ | prob_amplitude | input_state: BasicState or StateVector, | prob. amplitude (complex) | | | output_state: BasicState | | +----------------------+----------------------------------------------------------+---------------------------+ | probability | input_state: BasicState or StateVector, | probability (float [0;1]) | | | output_state: BasicState | | +----------------------+----------------------------------------------------------+---------------------------+ | probs | input_state: BasicState or StateVector | prob. distribution (BSD) | +----------------------+----------------------------------------------------------+---------------------------+ | probs_svd | input_dist: SVDistribution | Python dictionary | +----------------------+----------------------------------------------------------+---------------------------+ | probs_density_matrix | dm: DensityMatrix | Python dictionary | +----------------------+----------------------------------------------------------+---------------------------+ | evolve | input_state: BasicState or StateVector | evolved StateVector | +----------------------+----------------------------------------------------------+---------------------------+ | evolve_svd | input_state: SVDistribution or StateVector or BasicState | Python dictionary | +----------------------+----------------------------------------------------------+---------------------------+ | evolve_density_matrix| dm: DensityMatrix | Python dictionary | +----------------------+----------------------------------------------------------+---------------------------+ .. autoclass:: perceval.simulators.Simulator :members: :inherited-members: ================================================ FILE: docs/source/reference/simulators/simulator_factory.rst ================================================ SimulatorFactory ================ This class exposes a single :code:`build` static method that can be used to create a simulator suited to your circuit. In particular, it allows you to simulate circuits with non-unitary components or polarized components. >>> import perceval as pcvl >>> p = pcvl.Processor("SLOS", 2) >>> p.add(0, pcvl.BS()) >>> p.add(0, pcvl.TD(1)) # Add a non-unitary component >>> p.add(0, pcvl.BS()) >>> sim = pcvl.SimulatorFactory.build(p) # SLOS is transmitted from p to sim >>> sim.probs(pcvl.BasicState([1, 1])) { |1,0>: 0.25 |0,0>: 0.24999999999999994 |0,1>: 0.25 |0,2>: 0.12500000000000006 |2,0>: 0.12500000000000006 } The type of the simulator will depend on your components. However, it is guaranteed that the resulting simulator will have at least these methods: - :code:`set_selection` for heralds, postselect, and min_detected_photons_filter (already done if the circuit is a :code:`Processor`). - :code:`probs` to compute the output probabilities for one input state. - :code:`probs_svd` to compute the output probabilities for a :code:`SVDistribution`, with possible support for detectors The simulator factory aims at creating simulators for strong simulation, so it requires the backend to be capable of computing probability amplitudes. .. autoclass:: perceval.simulators.simulator_factory.SimulatorFactory :members: Simulator specificities ^^^^^^^^^^^^^^^^^^^^^^^ Depending on the components you have, there may be some specificities to the resulting simulator: Polarization ------------ If you have polarization components, then you will still be able to use :code:`evolve`. The input state will also be expected to be a polarized :code:`BasicState`, or, in the case of :code:`probs_svd`, a :code:`SVDistribution` with a single polarized non-superposed :code:`StateVector`. If the input is not polarized, the simulator will assume all photons have horizontal polarization. However, in any method, detectors won't be simulated if given. Time Delay and Loss ------------------- If you have :code:`TD` components or :code:`LC` components, the number of photons will not be conserved. You can still use :code:`evolve`, but you could expect strange results (do it at your own risks). Feed-forward ------------ If you have :code:`FFConfigurator` or :code:`FFCircuitProvider` in your circuit, you won't be able to call :code:`evolve`. Also, the dictionary returned by the call to :code:`probs_svd` will only have one performance field, named :code:`global_perf`. ================================================ FILE: docs/source/reference/simulators/stepper.rst ================================================ Stepper Simulator ================= The :code:`Stepper` is a special simulator that simulates the evolution of a :code:`StateVector` component by component. It's main purpose is to be able to see what the circuit does step by step. The internal behaviour of the :code:`Stepper` can be seen by using the debugger and going into the code, or by using the :code:`apply` method manually. .. warning:: The Stepper is much more slower than the usual :code:`Simulator`, as well as being less flexible, so it shouldn't be used for anything other than visualization. >>> import perceval as pcvl >>> c = pcvl.BS() // pcvl.PS(1.) // pcvl.BS() >>> sim = pcvl.Stepper(pcvl.SLOSBackend()) >>> sim.set_circuit(c) >>> state = pcvl.StateVector([1, 1]) >>> for r, component in c: >>> state = sim.apply(state, r, component) >>> print(component.describe(), state) BS.Rx() 0.707I*|2,0>+0.707I*|0,2> PS(phi=1) (-0.643-0.294I)*|2,0>+0.707I*|0,2> BS.Rx() (-0.321-0.501I)*|2,0>+(-0.292-0.455I)*|1,1>+(0.321+0.501I)*|0,2> The :code:`Stepper` can also be used to simulate :code:`LC` components, but the :code:`apply` method can't be used in that case (see the code of the :code:`compile` method to see how to do it manually) Note however that the :code:`Stepper` doesn't support annotated or polarized :code:`BasicState`, nor does it apply logical selection like heralding and post-selection on its own. It can still perform physical selection, including after simulating detectors. Also, when calling :code:`probs_svd` on the :code:`Stepper`, the returned dictionary has only the 'results' field. .. autoclass:: perceval.simulators.stepper.Stepper :members: :inherited-members: ================================================ FILE: docs/source/reference/utils/conversion.rst ================================================ conversion ^^^^^^^^^^ Perceval provides helper methods to convert the three types of results of the :ref:`Sampler` (namely the :ref:`BSDistribution`, :ref:`BSCount` and :ref:`BSSamples`) into each other. >>> import perceval as pcvl >>> distribution = pcvl.BSDistribution({pcvl.BasicState([1, 0]): 0.4, pcvl.BasicState([0, 1]): 0.6}) >>> print(pcvl.probs_to_sample_count(distribution, count=1000)) # Sampling noise is applied; results may vary { |1,0>: 392 |0,1>: 608 } Note that for methods converting from probabilities, passing a kwarg with a count is mandatory. This can either be: - :code:`count`, in which case this is the number of resulting samples. - :code:`max_shots` and/or :code:`max_samples`, in which case the one defined or the minimum of the two is the number of resulting samples. Conversion code reference ========================= .. automodule:: perceval.utils.conversion :members: ================================================ FILE: docs/source/reference/utils/density_matrix.rst ================================================ DensityMatrix ============= An other way of representing mixed states is through the Density Matrices formalism. You can create a density matrix from any 2-D array-like object and a mapping between the ``BasicStates`` and the indices of your array. Otherwise you can give a number of modes and photons and a mapping will be generated according to the ``FockBasis`` object. The easiest way to generate Density matrices remains to give any other quantum state class instance to the method ``from_svd()``. Many of the above methods extends to Density Matrices, such as ``sample`` and ``measure``. This class is also suited for non unitary evolution of the state. For example, the ``apply_loss`` method is already build-in to simulate the probabilistic loss of photons. It is also handled by the simulator for unitary evolutions with the method ``evolve_density_matrix``. The ``pdisplay()`` function also allows to easily see the coherence of your state by highlighting the non diagonal coefficients. For example, here is the resulting density matrix when we try to create a bell state with a Post-Selected CNOT: .. figure:: ../../_static/img/bell.svg :scale: 10 % :align: center And below, the result of the same circuit, but with a noisy ``Source``. We can see the presence of FockStates with a smaller number of photons. .. figure:: ../../_static/img/bell_noisy.svg :scale: 10 % :align: center .. autoclass:: perceval.utils.density_matrix.DensityMatrix :members: ================================================ FILE: docs/source/reference/utils/dist_metrics.rst ================================================ metrics ^^^^^^^ >>> import perceval as pcvl >>> dist_a = pcvl.BSDistribution({pcvl.BasicState([1, 0]): 0.4, pcvl.BasicState([0, 1]): 0.6}) >>> dist_b = pcvl.BSDistribution({pcvl.BasicState([1, 0]): 0.3, pcvl.BasicState([0, 1]): 0.7}) >>> print(pcvl.tvd_dist(dist_a, dist_b)) 0.1 >>> print(pcvl.kl_divergence(dist_a, dist_b)) 0.022582421084357485 Perceval provides ways to compare :ref:`BSDistribution` with mathematical metrics. .. automodule:: perceval.utils.dist_metrics :members: ================================================ FILE: docs/source/reference/utils/distinct_permutations.rst ================================================ distinct permutations ^^^^^^^^^^^^^^^^^^^^^ Perceval has a built-in method to construct efficiently the distinct permutations from an iterable. This method is comparable to the method from the more-itertools python package. >>> import perceval as pcvl >>> from perceval.utils.qmath import distinct_permutations >>> print([pcvl.BasicState(perm) for perm in distinct_permutations([1, 1, 0])]) # Generate all states with n = 2 and at most 1 photon per mode [|0,1,1>, |1,0,1>, |1,1,0>] .. autofunction:: perceval.utils.qmath.distinct_permutations ================================================ FILE: docs/source/reference/utils/expression.rst ================================================ Expression ========== This is a derived class from :code:`Parameter` that can hold mathematical expressions. An :code:`Expression` is automatically generated when using operators on :code:`Parameter` or :code:`Expression`. >>> import perceval as pcvl >>> a, b = pcvl.Parameter("a"), pcvl.Parameter("b") >>> e = a + b >>> e.spv a + b >>> a.set_value(1) >>> b.set_value(2) >>> float(e) 3.0 They can also be created manually to accept more general mathematical functions (using the sympy expression parsing) >>> phi = pcvl.Parameter("phi") >>> e = pcvl.Expression("cos(phi)", {phi}) # Declares phi as a sub-parameter. Equivalent to pcvl.E("cos(phi)", {phi}) >>> phi.set_value(0) >>> float(e) 1.0 .. autoclass:: perceval.utils.parameter.Expression :members: ================================================ FILE: docs/source/reference/utils/index.rst ================================================ utils ^^^^^ .. toctree:: matrix parameter states noise_model expression conversion logical_state dist_metrics distinct_permutations stategenerator polarization persistent_data random_seed density_matrix ../utils_algorithms/index logging ================================================ FILE: docs/source/reference/utils/logging.rst ================================================ utils.logging ============= To log with Perceval you can either use a built-in Perceval logger or the python one. By default, our logger will be used. To log the perceval messages with the python logger, use this method: .. code-block:: python from perceval.utils import use_python_logger use_python_logger() To switch back to using perceval logger, use: .. code-block:: python from perceval.utils import use_perceval_logger use_perceval_logger() .. note:: If you use the python logger, use directly the module logging of python to configure it (except for channel configuration) This module defines functions and classes which implement a flexible event logging system for any Perceval script. It is build to work similarly as the python logging module. Logger ------ A logger instance is created the first time Perceval is imported. In order to use it you'll just need to import it: .. code-block:: python from perceval.utils import get_logger To log a message, you can use it the same way as the python logger: .. code-block:: python from perceval.utils import get_logger get_logger().info('I log something as info') # or logger = get_logger() logger.info('1st message') logger.info('2nd message') Saving log to file ^^^^^^^^^^^^^^^^^^ By default the log are not saved in a file. If order to enable / disable the writing of logs in a file, use the following methods: .. code-block:: python from perceval.utils import get_logger get_logger().enable_file() get_logger().disable_file() When this feature is enabled, log files are written to Perceval persistent data folder and the path of the file will be printed when your script starts writing inside it. Levels ------ You can use the logger to log message at different level. Each level represent a different type of message. The level are listed by ascending order of importance in the following table. .. list-table:: :header-rows: 1 :stub-columns: 1 :width: 100% :align: center * - Log level - Perceval call - Usage * - DEBUG - ``level.debug`` - Detailed technical information, typically of interest only when diagnosing problems. * - INFO - ``level.info`` - Confirmation of things working as expected. * - WARNING - ``level.warn`` - An indication that something unexpected happened, or indicative of some problem in the near future. The software is still working as expected. * - ERROR - ``level.err`` - Due to a more serious problem, the software has not been able to perform some function. * - CRITICAL - ``level.critical`` - A serious error, indicating that the program itself is unable to continue running normally or has crashed. Example ^^^^^^^ .. code-block:: python from perceval.utils import get_logger get_logger().info('I log something as info') get_logger().critical('I log something as critical') Channels -------- You can also log in a specific channel. A channel is like a category. Each channel can have its own configuration, which means each channel can have a different level. If the channel is not specified, the message is logged in the ``user`` channel. .. note:: If you are a Perceval user, you should only write log in the channel ``user``. .. list-table:: :header-rows: 1 :stub-columns: 1 :width: 100% :align: center * - Channel - Default level - Usage * - ``general`` - off - General info: Technical info, track the usage of features * - ``resources`` - off - Usage info about our backends or remote platform GPU (exqalibur) * - ``user`` - warning - Channel to use as a Perceval user & warnings (such as deprecated methods or arguments) Example ^^^^^^^ .. code-block:: python from perceval.utils.logging import get_logger, channel get_logger().info('I log something as info in channel user', channel.user) To set a level for a channel, use the following method: .. code-block:: python from perceval.utils import get_logger get_logger().set_level(level, channel) Example ^^^^^^^ .. code-block:: python from perceval.utils.logging import get_logger, level, channel get_logger().set_level(level.info, channel.general) Logger configuration -------------------- For logging to be useful, it needs to be configured, meaning setting the levels for each channel and if log are saved in a file. Setting a level for a channel means that any log with a less important level will not be displayed/saved. In most cases, only the user & general channel needs to be so configured, since all relevant messages will be logged here. Example ^^^^^^^ .. code-block:: python from perceval.utils.logging import get_logger, channel, level logger = get_logger() logger.enable_file() logger.set_level(level.info, channel.resources) logger.set_level(level.err, channel.general) .. note:: The logger configuration can be saved on your hard drive so you don't have to configure the logger each time you use perceval. When saved, it is written to a file in Perceval persistent data folder. In order to configure it you have to use the :class:`LoggerConfig`. .. automodule:: perceval.utils.logging.config :members: After configuring your LoggerConfig, you can apply it to the current logger: .. code-block:: python from perceval.utils.logging import get_logger, LoggerConfig, level, channel logger_config = LoggerConfig() logger_config.enable_file() logger_config.set_level(level.info, channel.user) get_logger().apply_config(logger_config) Log format ---------- On the console the log will appear with the format: [log_level] message In the file, the log will be save to the format: [yyyy-mm-dd HH:MM:SS.fff]channel_first_letter[level_first_letter] message Log exceptions -------------- If the general channel level is at least on critical and save in file is enable, uncaught exception will be logged and saved on disk with their callstack. ================================================ FILE: docs/source/reference/utils/logical_state.rst ================================================ Logical State ============= A :code:`LogicalState` represents a pure qubit state. It is a list of 0s and 1s. Their main purpose is to provide an easy way to convert a qubit state to a :code:`BasicState`. They can be used in two ways: - With :code:`Port` >>> import perceval as pcvl >>> encodings = [pcvl.Encoding.DUAL_RAIL, pcvl.Encoding.QUDIT2] >>> ports = [pcvl.Port(encoding, "my_name") for encoding in encodings] >>> ls = pcvl.LogicalState("101") >>> print(pcvl.get_basic_state_from_ports(ports, ls)) |0,1,0,1,0,0> - With :code:`Processor`, :code:`Experiment` or :code:`RemoteProcessor` that has Ports defined (recommended when using composition): >>> import perceval as pcvl >>> encodings = [pcvl.Encoding.DUAL_RAIL, pcvl.Encoding.QUDIT2] >>> e = pcvl.Experiment(6) >>> m = 0 >>> for i, encoding in enumerate(encodings): >>> e.add_port(m, pcvl.Port(encoding, f"{i}")) >>> m += encoding.fock_length >>> ls = pcvl.LogicalState([1, 0, 1]) >>> e.with_input(ls) >>> print(e.input_state) |0,1,0,1,0,0> Note that the way a :code:`LogicalState` is converted depends on the encoding, and the number of modes, photons, and the expected number of qubits is only guaranteed by the encoding, not by the conversion itself. .. note:: The perceval convention for LogicalStates is that the first digit is represented in the first mode(s) of a circuit. .. autoclass:: perceval.utils.logical_state.LogicalState :members: .. autofunction:: perceval.utils.logical_state.generate_all_logical_states ================================================ FILE: docs/source/reference/utils/matrix.rst ================================================ Matrix ====== This class is used to represent both numeric and symbolic complex matrices. Every matrix in perceval is an instance of this class. >>> import perceval as pcvl >>> M = pcvl.Matrix("1 2 3\n4 5 6\n7 8 9") >>> pcvl.pdisplay(M) ⎡1 2 3⎤ ⎢4 5 6⎥ ⎣7 8 9⎦ >>> M.is_unitary() False It also comes with utility methods to create unitary matrices >>> random_unitary = pcvl.Matrix.random_unitary(6) # 6*6 >>> deterministic_unitary = pcvl.Matrix.parametrized_unitary(list(range(8))) # 2*2 (requires 2*m**2 parameters) >>> from_array_unitary = pcvl.Matrix.get_unitary_extension(numpy_2d_array) # Size (row+col) * (row+col) .. autoclass:: perceval.utils.matrix.Matrix :members: :special-members: __new__ ================================================ FILE: docs/source/reference/utils/noise_model.rst ================================================ Noise Model =========== >>> import perceval as pcvl >>> noise_model = pcvl.NoiseModel(brightness=0.3, g2=0.05) >>> proc = pcvl.Processor("SLOS", 4, noise_model) .. autoclass:: perceval.utils.noise_model.NoiseModel :members: :inherited-members: ================================================ FILE: docs/source/reference/utils/parameter.rst ================================================ Parameter ========= This class holds parameter values that can be used in circuits and in backends supporting symbolic computation. >>> import perceval as pcvl >>> p = pcvl.Parameter("phi") # Or equivalently pcvl.P("phi") >>> p.spv phi >>> p.defined False >>> p.set_value(3.14) >>> float(p) 3.14 >>> p.defined True When defining the parameter, you can also set its numerical value, max/min boundaries and periodicity: >>> import perceval as pcvl >>> import math >>> alpha = pcvl.P("phi", min_v=0, max_v=2*math.pi, periodic=True) >>> alpha.is_periodic True .. autoclass:: perceval.utils.parameter.Parameter :members: ================================================ FILE: docs/source/reference/utils/persistent_data.rst ================================================ PersistentData ============== :code:`PersistentData` is a class that allows to save and store data across perceval launches. Most importantly, it is used to save :ref:`RemoteConfig`, :ref:`JobGroup`... .. warning:: The folder created by :code:`PersistentData` is never emptied automatically. This means that using features that make use of :code:`PersistentData` may use a lot of disk after many uses. Usage example: >>> import perceval as pcvl >>> pdata = pcvl.PersistentData() >>> pdata.write_file("my_file.txt", "my_data", pcvl.FileFormat.TEXT) >>> print(pdata.read_file("my_file.txt", pcvl.FileFormat.TEXT)) my_data >>> pdata.delete_file("my_file.txt") .. note:: The default folder is created inside the user folder, so the persistent data are not shared between users. .. autoclass:: perceval.utils.persistent_data.PersistentData :members: .. autoenum:: perceval.utils._enums.FileFormat ================================================ FILE: docs/source/reference/utils/polarization.rst ================================================ Polarization ============ Polarization encoding is stored in :ref:`AnnotatedFockState` objects as a special ``P`` annotation. Their value follows `Jones calculus `_. Annotations values are represented by two angles :math:`(\theta, \phi)`. The representation of the polarization in :math:`\begin{pmatrix}E_h\\E_v\end{pmatrix}` basis is obtained by applying Jones conversion: :math:`\overrightarrow{J}=\begin{pmatrix}\cos \frac{\theta}{2}\\e^{i\phi}\sin \frac{\theta}{2}\end{pmatrix}`. The same can also be noted: :math:`\cos \frac{\theta}{2}\ket{H}+e^{i\phi}\sin \frac{\theta}{2}\ket{V}`. For instance, the following defines a polarization with :math:`\theta=\frac{\pi}{2},\phi=\frac{\pi}{4}` corresponding to Jones vector: :math:`\begin{pmatrix}\cos \frac{\pi}{4}\\e^{i\frac{\pi}{4}}\sin \frac{\pi}{4}\end{pmatrix}` .. code-block:: python >>> import perceval as pcvl, sympy as sp >>> >>> p = pcvl.Polarization((sp.pi/2, sp.pi/4)) >>> p.project_eh_ev() (0.707106, 0.5+0.5j) It is also possible to use ``H``, ``V``, ``D``, ``A``, ``L`` and ``R`` as shortcuts to predefined values: .. list-table:: :header-rows: 1 * - Code - :math:`(\phi,\theta)` - Jones vector * - ``H`` - :math:`(0,0)` - :math:`\begin{pmatrix}1\\0\end{pmatrix}` * - ``V`` - :math:`(\pi,0)` - :math:`\begin{pmatrix}0\\1\end{pmatrix}` * - ``D`` - :math:`(\frac{\pi}{2},0)` - :math:`\frac{1}{\sqrt 2}\begin{pmatrix}1\\1\end{pmatrix}` * - ``A`` - :math:`(\frac{\pi}{2},\pi)` - :math:`\frac{1}{\sqrt 2}\begin{pmatrix}1\\-1\end{pmatrix}` * - ``L`` - :math:`(\frac{\pi}{2},\frac{\pi}{2})` - :math:`\frac{1}{\sqrt 2}\begin{pmatrix}1\\i\end{pmatrix}` * - ``R`` - :math:`(\frac{\pi}{2},\frac{3\pi}{2})` - :math:`\frac{1}{\sqrt 2}\begin{pmatrix}1\\-i\end{pmatrix}` .. code-block:: python >>> p = pcvl.Polarization("D") >>> p.theta_phi (pi/2, 0) >>> p.project_eh_ev() (sqrt(2)/2, sqrt(2)/2) Defining states with polarization is as simple as using the ``P`` special annotation: .. code-block:: python >>> st2 = pcvl.BasicState("|{P:H},{P:V}>") >>> st2 = pcvl.BasicState("|{P:(sp.pi/2,sp.pi/3)>") If polarization is used for any photon in the state, the state is considered as using polarization: .. code-block:: python >>> pcvl.AnnotatedFockState("|{P:H},0,{P:V}>").has_polarization True >>> pcvl.AnnotatedFockState("|{P:V},0,1>").has_polarization True >>> pcvl.AnnotatedFockState("|{a:0},0,{a:1}>").has_polarization False .. note:: To simplify the notation: * linear polarization can be defined with a single parameter: ``{P:sp.pi/2}`` is equivalent to ``{P:(sp.pi/2,0)}`` * if the polarization annotation is omitted for some photons, these photons will be considered as having a horizontal polarization. Polarization code reference --------------------------- .. automodule:: perceval.utils.polarization :members: ================================================ FILE: docs/source/reference/utils/random_seed.rst ================================================ random_seed =========== To achieve a reproducible result, the :code:`pcvl.random_seed()` function can be used before a given computation. This function ensures that any random numbers used in algorithms will be the same from run to run. Example: >>> from perceval import random_seed >>> import random >>> >>> random_seed(2) # Set the seed to 2 >>> print(random.random()) 0.9478274870593494 >>> print(random.random()) 0.9560342718892494 >>> random_seed(2) # Reset the seed to 2 >>> print(random.random()) 0.9478274870593494 >>> print(random.random()) 0.9560342718892494 The random real numbers drawn are in the same order when the seed is fixed to the same value. .. autofunction:: perceval.utils._random.random_seed ================================================ FILE: docs/source/reference/utils/stategenerator.rst ================================================ State Generator =============== This class provides a way to generate usual StateVectors with a given encoding. >>> from perceval import StateGenerator, Encoding >>> sg = StateGenerator(Encoding.DUAL_RAIL) >>> sg.bell_state("phi+") 0.707*|1,0,1,0>+0.707*|0,1,0,1> >>> sg.dicke_state(2) 0.408*|1,0,0,1,0,1,1,0>+0.408*|0,1,1,0,1,0,0,1>+0.408*|0,1,1,0,0,1,1,0>+0.408*|0,1,0,1,1,0,1,0>+0.408*|1,0,0,1,1,0,0,1>+0.408*|1,0,1,0,0,1,0,1> .. autoclass:: perceval.utils.stategenerator.StateGenerator :members: ================================================ FILE: docs/source/reference/utils/states.rst ================================================ States ====== States hold the quantum data. Perceval introduces a formalism to represent all kinds of quantum states. Basic State ----------- Basic states describe Fock states of :math:`n` photons over :math:`m` modes where photons can be annotated. If none is annotated, then all photons in the state are indistinguishable. It is represented by ``|n_1,n_2,...,n_m>`` notation where ``n_k`` is the number of photons in mode *k*. Technichally, the :code:`BasicState` initializer is implemented as a factory able to return any of the following types: * :code:`FockState`: A light-weight object only containing photon positions in mode (e.g. :code:`|1,0,1>`). Can be used to represent detections. It is an alias of exqalibur :ref:`FockState`. * :code:`NoisyFockState`: A collection of indistinguishable photon groups, that are totally distinguishable. The distinguishability index is an integer and is referred to as the `noise tag` (e.g. :code:`|{0},{1},{0}{2}>` contains three groups of indistinguishable photons tagged 0, 1 and 2). It is an alias of exqalibur :ref:`NoisyFockState`. * :code:`AnnotatedFockState`: Replace the previous :code:`FockState` by allowing rich annotations, having one or more string types, each having a complex number for value. This enables to accurately encode physical parameters and play with partial distinguishability (e.g. :code:`|{P:H,lambda:0.625},{P:V,lambda:0.618}>`). Please note that apart from polarisation, `Perceval` does not provide a generic algorithm to separate rich annotated states, and the user would have to write one. It is an alias of exqalibur :ref:`AnnotatedFockState` (see also: :code:`exqalibur.Annotation`). Simple code example with indistinguishable photons: >>> import perceval as pcvl >>> >>> # Create a two-mode FockState with no photon in the 1st mode, and 1 photon in the 2nd mode. >>> bs = pcvl.BasicState("|0,1>") >>> print(bs) # Prints out the created Fock state |0,1> >>> bs.n # Displays the number of photons of the created Fock state 1 >>> bs.m # Displays the number of modes of the created Fock state 2 >>> bs[0] # Displays the number of photons in the first mode of the created Fock state ( note that the counter of the number of modes starts at 0 and ends at m-1 for an m-mode Fock state) 0 >>> print(pcvl.BasicState([0,1])*pcvl.BasicState([2,3])) # Tensors the |0,1> and |2,3> Fock states, and prints out the result (the Fock state |0,1,2,3>) |0,1,2,3> State Vector ------------ :code:`StateVector` represents a pure state. It is a (complex) linear combination of any of the :code:`FockState` types to represent state superposition. It is an alias of exqalibur :ref:`StateVector` class. Basic State Samples ------------------- The class :code:`BSSamples` is a container that collects chronologically ordered sampled states. It is, for instance, the object generated by a sampling method, such as ``samples`` command. It is an alias of exqalibur :ref:`BSSamples` class and only stores perfect :code:`FockState`. Basic State Count ----------------- The class :code:`BSCount` is also a container but it only counts the states without keeping in track their order. The ``sample_count`` command return this data type. It is an alias of exqalibur :ref:`BSCount` class and only stores perfect :code:`FockState`. Basic State Distribution ------------------------ The class :code:`BSDistribution` represents a probability distribution of measured states. It maps states with their associated probability. It is the type of object returned by a ``probs`` command. It is an alias of exqalibur :ref:`BSDistribution` class and only stores perfect :code:`FockState`. State Vector Distribution ------------------------- :code:`SVDistribution` is a recipe for constructing a mixed state using ``BasicState`` and/or ``StateVector`` as components. For example, The following ``SVDistribution`` +-------------------------------------+------------------+ | ``state`` | ``probability`` | +=====================================+==================+ | ``|0,1>`` | ``1/2`` | +-------------------------------------+------------------+ | ``1/sqrt(2)*|1,0>+1/sqrt(2)*|0,1>`` | ``1/4`` | +-------------------------------------+------------------+ | ``|1,0>`` | ``1/4`` | +-------------------------------------+------------------+ results in the mixed state :math:`\frac{1}{2}\ket{0,1}\bra{0,1} + \frac{1}{4}(\frac{1}{\sqrt{2}}\ket{1,0} + \frac{1}{\sqrt{2}}\ket{0,1})(\frac{1}{\sqrt{2}}\bra{1,0} + \frac{1}{\sqrt{2}}\bra{0,1}) + \frac{1}{4}\ket{1,0}\bra{1,0}` It is an alias of exqalibur :ref:`SVDistribution` class. .. WARNING:: ``BSDistribution``, ``SVDistribution`` and ``BSCount`` are NOT ordered data structures and must NOT be indexed with integers. States generators ----------------- .. autofunction:: perceval.utils.states.allstate_iterator .. autofunction:: perceval.utils.states.max_photon_state_iterator ================================================ FILE: docs/source/reference/utils_algorithms/circuit_optimizer.rst ================================================ Circuit Optimizer ================= .. autoclass:: perceval.utils.algorithms.circuit_optimizer.CircuitOptimizer :members: :inherited-members: ================================================ FILE: docs/source/reference/utils_algorithms/index.rst ================================================ utils.algorithms ^^^^^^^^^^^^^^^^ .. toctree:: circuit_optimizer simplify ================================================ FILE: docs/source/reference/utils_algorithms/simplify.rst ================================================ simplify ======== Circuit simplification ^^^^^^^^^^^^^^^^^^^^^^ Several strategies to simplify a circuit exist. Perceval circuit simplification takes a circuit and does the following: * For phase shifters, add their phase if they are not parameters and combine them into a single phase shifter (work through permutations). If :code:`display == False`, removes them if their added phase is :math:`0` or :math:`2\pi`. * For Permutations, if two permutations are consecutive, they are combined into a single permutation. For single permutations, fixed modes at the extremities are removed. If they are not just consecutive, try to compute a "better" permutation, then if it is better, move the components accordingly to this new permutation. Display changes how a permutation is evaluated. Example: >>> from perceval.utils.algorithms.simplification import simplify >>> from perceval import Circuit, PERM, PS >>> circuit = Circuit(6) // PERM([3,2,1,0]) // (1, PERM([4,1,3,2,0])) // PS(phi=0.6) // PS(phi=0.2) >>> print(circuit.ncomponents()) 4 >>> simplified_circuit = simplify(circuit, display = False) >>> print(simplified_circuit.ncomponents()) 2 simplify code reference ^^^^^^^^^^^^^^^^^^^^^^^ .. autofunction:: perceval.utils.algorithms.simplification.simplify ================================================ FILE: docs/source/references.bib ================================================ @article{clement2022lov, title = {LOv-Calculus: A Graphical Language for Linear Optical Quantum Circuits}, author = {Cl{\'e}ment, Alexandre and Heurtel, Nicolas and Mansfield, Shane and Perdrix, Simon and Valiron, Beno{\^\i}t}, journal = {arXiv preprint arXiv:2204.11787}, year = {2022} } @article{schollwock2011density, title = {The density-matrix renormalization group in the age of matrix product states}, author = {Schollw{\"o}ck, Ulrich}, journal = {Annals of physics}, volume = {326}, number = {1}, pages = {96--192}, year = {2011}, publisher = {Elsevier} } @article{oh2021classical, title = {Classical simulation of lossy boson sampling using matrix product operators}, author = {Oh, Changhun and Noh, Kyungjoo and Fefferman, Bill and Jiang, Liang}, journal = {Physical Review A}, volume = {104}, number = {2}, pages = {022407}, year = {2021}, publisher = {APS} } @article{bradler_certain_2021, title = {Certain properties and applications of shallow bosonic circuits}, journal={arXiv preprint arXiv:2112.09766}, url = {https://arxiv.org/abs/2112.09766v1}, doi = {10.48550/arXiv.2112.09766}, abstract = {We introduce a novel approach to solve optimization problems on a boson sampling device assisted by classical machine-learning techniques. By virtue of the parity function, we map all measurement patterns, which label the basis spanning an \$M\$-mode bosonic Hilbert space, to the Hilbert space of \$M\$ qubits. As a result, the sampled probability function can be interpreted as a result of sampling a multiqubit circuit. The method is presented on several instances of a QUBO/Ising problem as well as portfolio optimization problems. Among many demonstrated properties of the parity function is the ability to chart the entire qubit Hilbert space no matter how shallow the initial bosonic circuits is. In order to show this we link boson sampling circuits to a class of finite Young's lattices (a special poset with the so-called Ferrers diagrams ordered by inclusion), Boolean lattices and the properties of Dyck/staircase paths on integer lattices. Our results and methods can be applied to a large variety of photonic circuits, including the deep ones of essentially any geometry, but our main focus is on shallow circuits as they are less affected by photon loss and relatively easy to implement in the form of a time-bin interferometer.}, language = {en}, author = {Bradler, Kamil and Wallner, Hugo}, month = dec, year = {2021}, } @article{hadfield2009single, title = {Single-photon detectors for optical quantum information applications}, author = {Hadfield, Robert H}, journal = {Nature photonics}, volume = {3}, number = {12}, pages = {696--705}, year = {2009}, publisher = {Nature Publishing Group} } @article{knill_quantum_2002, title = {Quantum gates using linear optics and postselection}, volume = {66}, url = {https://link.aps.org/doi/10.1103/PhysRevA.66.052306}, eprint = {http://arxiv.org/abs/quant-ph/0110144}, doi = {10.1103/PhysRevA.66.052306}, abstract = {Recently it was realized that linear optics and photodetectors with feedback can be used for theoretically efficient quantum information processing. The first of three steps toward efficient linear optics quantum computation is to design a simple postselected gate that implements a nonlinear phase shift on one mode. Here a computational strategy is given for finding postselected gates for bosonic qubits with helper photons. A more efficient conditional sign flip gate is obtained. What is the maximum efficiency for such gates? This question is posed and it is shown that the probability of success cannot be 1.}, number = {5}, journal = {Physical Review A}, author = {Knill, E.}, month = nov, year = {2002}, note = {Publisher: American Physical Society}, pages = {052306}, } @article{ralph_simple_2001, title = {Simple scheme for efficient linear optics quantum gates}, volume = {65}, url = {https://link.aps.org/doi/10.1103/PhysRevA.65.012314}, doi = {10.1103/PhysRevA.65.012314}, abstract = {We describe the construction of a conditional quantum control-NOT (CNOT) gate from linear optical elements following the program of Knill, Laflamme, and Milburn [Nature 409, 46 (2001)]. We show that the basic operation of this gate can be tested using current technology. We then simplify the scheme significantly.}, number = {1}, journal = {Physical Review A}, author = {Ralph, T. C. and White, A. G. and Munro, W. J. and Milburn, G. J.}, month = dec, year = {2001}, note = {Publisher: American Physical Society}, pages = {012314}, } @article{ralph_linear_2002, title = {Linear optical controlled-{NOT} gate in the coincidence basis}, volume = {65}, url = {https://link.aps.org/doi/10.1103/PhysRevA.65.062324}, doi = {10.1103/PhysRevA.65.062324}, abstract = {We describe the operation and tolerances of a nondeterministic, coincidence basis, quantum controlled-NOT gate for photonic qubits. It is constructed solely from linear optical elements and requires only a two-photon source for its demonstration. Its success probability is 1/9.}, number = {6}, journal = {Physical Review A}, author = {Ralph, T. C. and Langford, N. K. and Bell, T. B. and White, A. G.}, month = jun, year = {2002}, note = {Publisher: American Physical Society}, pages = {062324}, } @inproceedings{grover_fast_1996, address = {New York, NY, USA}, series = {{STOC} '96}, title = {A fast quantum mechanical algorithm for database search}, isbn = {978-0-89791-785-8}, url = {https://doi.org/10.1145/237814.237866}, doi = {10.1145/237814.237866}, booktitle = {Proceedings of the twenty-eighth annual {ACM} symposium on {Theory} of {Computing}}, publisher = {Association for Computing Machinery}, author = {Grover, Lov K.}, month = jul, year = {1996}, pages = {212--219}, } @article{bennett_quantum_2014, series = {Theoretical {Aspects} of {Quantum} {Cryptography} – celebrating 30 years of {BB84}}, title = {Quantum cryptography: {Public} key distribution and coin tossing}, volume = {560}, issn = {0304-3975}, shorttitle = {Quantum cryptography}, url = {https://www.sciencedirect.com/science/article/pii/S0304397514004241}, doi = {10.1016/j.tcs.2014.05.025}, language = {en}, journal = {Theoretical Computer Science}, author = {Bennett, Charles H. and Brassard, Gilles}, month = dec, year = {2014}, pages = {7--11}, } @article{ekert_quantum_1991, title = {Quantum cryptography based on {Bell}'s theorem}, volume = {67}, url = {https://link.aps.org/doi/10.1103/PhysRevLett.67.661}, doi = {10.1103/PhysRevLett.67.661}, abstract = {Practical application of the generalized Bell’s theorem in the so-called key distribution process in cryptography is reported. The proposed scheme is based on the Bohm’s version of the Einstein-Podolsky-Rosen gedanken experiment and Bell’s theorem is used to test for eavesdropping., This article appears in the following collection:}, number = {6}, journal = {Physical Review Letters}, author = {Ekert, Artur K.}, month = aug, year = {1991}, note = {Publisher: American Physical Society}, pages = {661--663}, } @article{harrow_quantum_2009, title = {Quantum {Algorithm} for {Linear} {Systems} of {Equations}}, volume = {103}, url = {https://link.aps.org/doi/10.1103/PhysRevLett.103.150502}, doi = {10.1103/PhysRevLett.103.150502}, abstract = {Solving linear systems of equations is a common problem that arises both on its own and as a subroutine in more complex problems: given a matrix A and a vector →b, find a vector →x such that A→x=→b. We consider the case where one does not need to know the solution →x itself, but rather an approximation of the expectation value of some operator associated with →x, e.g., →x†M→x for some matrix M. In this case, when A is sparse, N×N and has condition number κ, the fastest known classical algorithms can find →x and estimate →x†M→x in time scaling roughly as N√κ. Here, we exhibit a quantum algorithm for estimating →x†M→x whose runtime is a polynomial of log (N) and κ. Indeed, for small values of κ [i.e., polylog (N)], we prove (using some common complexity-theoretic assumptions) that any classical algorithm for this problem generically requires exponentially more time than our quantum algorithm.}, number = {15}, journal = {Physical Review Letters}, author = {Harrow, Aram W. and Hassidim, Avinatan and Lloyd, Seth}, month = oct, year = {2009}, note = {Publisher: American Physical Society}, pages = {150502}, } @article{kwiat_grovers_2000, title = {Grover's search algorithm: {An} optical approach}, volume = {47}, issn = {0950-0340}, shorttitle = {Grover's search algorithm}, url = {https://www.tandfonline.com/doi/abs/10.1080/09500340008244040}, doi = {10.1080/09500340008244040}, abstract = {The essential operations of a quantum computer can be accomplished using solely optical elements, with different polarization or spatial modes representing the individual qubits. We present a simple all-optical implementation of Grover's algorithm for efficient searching, in which a database of four elements is searched with a single query. By ‘compiling’ the actual set-up, we have reduced the required number of optical elements from 24 to only 12. We discuss the extension to large databases, and the limitations of these techniques.}, number = {2-3}, journal = {Journal of Modern Optics}, author = {Kwiat, P. G. and Mitchell, J. R. and Schwindt, P. D. D. and White, A. G.}, month = feb, year = {2000}, pages = {257--266}, } @inproceedings{yee_fock_2021, title = {Fock {State}-enhanced {Expressivity} of {Quantum} {Machine} {Learning} {Models}}, abstract = {We propose quantum classifiers based on encoding classical data onto Fock states using tunable beam-splitter meshes, similar to the boson sampling architecture. We show that higher photon numbers enhance the expressive power of the circuit.}, booktitle = {2021 {Conference} on {Lasers} and {Electro}-{Optics} ({CLEO})}, author = {Yee, Gan Beng and Leykam, Daniel and Angelakis, Dimitris G.}, month = may, year = {2021}, note = {ISSN: 2160-8989}, keywords = {Electrooptical waveguides, Encoding, Integrated circuit modeling, Laser modes, Machine learning, Photonics}, pages = {1--2}, } @article{peruzzo_variational_2014, title = {A variational eigenvalue solver on a photonic quantum processor}, volume = {5}, copyright = {2014 The Author(s)}, issn = {2041-1723}, url = {https://www.nature.com/articles/ncomms5213}, doi = {10.1038/ncomms5213}, abstract = {Quantum computers promise to efficiently solve important problems that are intractable on a conventional computer. For quantum systems, where the physical dimension grows exponentially, finding the eigenvalues of certain operators is one such intractable problem and remains a fundamental challenge. The quantum phase estimation algorithm efficiently finds the eigenvalue of a given eigenvector but requires fully coherent evolution. Here we present an alternative approach that greatly reduces the requirements for coherent evolution and combine this method with a new approach to state preparation based on ansätze and classical optimization. We implement the algorithm by combining a highly reconfigurable photonic quantum processor with a conventional computer. We experimentally demonstrate the feasibility of this approach with an example from quantum chemistry—calculating the ground-state molecular energy for He–H+. The proposed approach drastically reduces the coherence time requirements, enhancing the potential of quantum resources available today and in the near future.}, language = {en}, number = {1}, journal = {Nature Communications}, author = {Peruzzo, Alberto and McClean, Jarrod and Shadbolt, Peter and Yung, Man-Hong and Zhou, Xiao-Qi and Love, Peter J. and Aspuru-Guzik, Alán and O’Brien, Jeremy L.}, month = jul, year = {2014}, keywords = {Applied physics, Quantum chemistry, Quantum optics}, pages = {4213}, } @article{walschaers2016statistical, title = {Statistical benchmark for BosonSampling}, author = {Walschaers, Mattia and Kuipers, Jack and Urbina, Juan-Diego and Mayer, Klaus and Tichy, Malte Christopher and Richter, Klaus and Buchleitner, Andreas}, journal = {New Journal of Physics}, volume = {18}, number = {3}, pages = {032001}, year = {2016}, publisher = {IOP Publishing} } @article{tichy2014stringent, title = {Stringent and efficient assessment of boson-sampling devices}, author = {Tichy, Malte C and Mayer, Klaus and Buchleitner, Andreas and M{\o}lmer, Klaus}, journal = {Physical review letters}, volume = {113}, number = {2}, pages = {020502}, year = {2014}, publisher = {APS} } @article{shchesnovich2016universality, title = {Universality of generalized bunching and efficient assessment of boson sampling}, author = {Shchesnovich, VS}, journal = {Physical review letters}, volume = {116}, number = {12}, pages = {123601}, year = {2016}, publisher = {APS} } @article{clifford2020faster, title = {Faster classical boson sampling}, author = {Clifford, Peter and Clifford, Rapha{\"e}l}, journal = {arXiv preprint arXiv:2005.04214}, year = {2020}, url = {https://arxiv.org/abs/2005.04214}, } @incollection{clifford_classical_2018, series = {Proceedings}, title = {The {Classical} {Complexity} of {Boson} {Sampling}}, url = {https://epubs.siam.org/doi/abs/10.1137/1.9781611975031.10}, abstract = {Multilayer network analysis is a useful approach for studying networks of entities that interact with each other via multiple relationships. Classifying the importance of nodes and node-layer tuples is an important aspect of the study of multilayer networks. To do this, it is common to calculate various centrality measures, which allow one to rank nodes and node-layers according to a variety of structural features. In this paper, we formulate occupation, PageRank, betweenness, and closeness centralities in terms of node-occupation properties of different types of continuous-time classical and quantum random walks on multilayer networks. We apply our framework to a variety of synthetic and real-world multilayer networks, and we identify notable differences between classical and quantum centrality measures. Our computations give insights into the correlations between certain centralities that are based on random walks and associated centralities that are based on geodesic paths.}, booktitle = {Proceedings of the 2018 {Annual} {ACM}-{SIAM} {Symposium} on {Discrete} {Algorithms} ({SODA})}, publisher = {Society for Industrial and Applied Mathematics}, author = {Clifford, Peter and Clifford, Raphaël}, month = jan, year = {2018}, doi = {10.1137/1.9781611975031.10}, pages = {146--155}, } @inproceedings{aaronson_computational_2011, address = {New York, NY, USA}, series = {{STOC} '11}, title = {The computational complexity of linear optics}, isbn = {978-1-4503-0691-1}, url = {https://doi.org/10.1145/1993636.1993682}, doi = {10.1145/1993636.1993682}, abstract = {We give new evidence that quantum computers -- moreover, rudimentary quantum computers built entirely out of linear-optical elements -- cannot be efficiently simulated by classical computers. In particular, we define a model of computation in which identical photons are generated, sent through a linear-optical network, then nonadaptively measured to count the number of photons in each mode. This model is not known or believed to be universal for quantum computation, and indeed, we discuss the prospects for realizing the model using current technology. On the other hand, we prove that the model is able to solve sampling problems and search problems that are classically intractable under plausible assumptions. Our first result says that, if there exists a polynomial-time classical algorithm that samples from the same probability distribution as a linear-optical network, then P\#P=BPPNP, and hence the polynomial hierarchy collapses to the third level. Unfortunately, this result assumes an extremely accurate simulation. Our main result suggests that even an approximate or noisy classical simulation would already imply a collapse of the polynomial hierarchy. For this, we need two unproven conjectures: the Permanent-of-Gaussians Conjecture, which says that it is \#P-hard to approximate the permanent of a matrix A of independent N(0,1) Gaussian entries, with high probability over A; and the Permanent Anti-Concentration Conjecture, which says that {\textbar}Per(A){\textbar}{\textgreater}=√(n!)poly(n) with high probability over A. We present evidence for these conjectures, both of which seem interesting even apart from our application. This paper does not assume knowledge of quantum optics. Indeed, part of its goal is to develop the beautiful theory of noninteracting bosons underlying our model, and its connection to the permanent function, in a self-contained way accessible to theoretical computer scientists.}, booktitle = {Proceedings of the forty-third annual {ACM} symposium on {Theory} of computing}, publisher = {Association for Computing Machinery}, author = {Aaronson, Scott and Arkhipov, Alex}, month = jun, year = {2011}, keywords = {\#p, BGP, bosons, linear optics, permanent, polynomial hierarchy, random self-reducibility, sampling}, pages = {333--342}, } @article{valiant_complexity_1979, title = {The complexity of computing the permanent}, volume = {8}, issn = {0304-3975}, url = {https://www.sciencedirect.com/science/article/pii/0304397579900446}, doi = {10.1016/0304-3975(79)90044-6}, abstract = {It is shown that the permanent function of (0, 1)-matrices is a complete problem for the class of counting problems associated with nondeterministic polynomial time computations. Related counting problems are also considered. The reductions used are characterized by their nontrivial use of arithmetic.}, language = {en}, number = {2}, journal = {Theoretical Computer Science}, author = {Valiant, L. G.}, month = jan, year = {1979}, pages = {189--201}, } @article{mezher_assessing_2022, title = {Assessing the quality of near-term photonic quantum devices}, url = {http://arxiv.org/abs/2202.04735}, abstract = {For near-term quantum devices, an important challenge is to develop efficient methods to certify that noise levels are low enough to allow potentially useful applications to be carried out. We present such a method tailored to photonic quantum devices consisting of single photon sources coupled to linear optical circuits coupled to photon detectors. It uses the output statistics of BosonSampling experiments with input size \$n\$ (\$n\$ input photons in the ideal case). We propose a series of benchmark tests targetting two main sources of noise, namely photon loss and distinguishability. Our method results in a single-number metric, the Photonic Quality Factor, defined as the largest number of input photons for which the output statistics pass all tests. We provide strong evidence that passing all tests implies that our experiments are not efficiently classically simulable, by showing how several existing classical algorithms for efficiently simulating noisy BosonSampling fail the tests. Finally we show that BosonSampling experiments with average photon loss rate per mode scaling as \$o(1)\$ and average fidelity of \$ (1-o({\textbackslash}frac\{1\}\{n{\textasciicircum}6\})){\textasciicircum}2\$ between any two single photon states is sufficient to keep passing our tests. Unsurprisingly, our results highlight that scaling in a manner that avoids efficient classical simulability will at some point necessarily require error correction and mitigation.}, journal = {arXiv:2202.04735 [quant-ph]}, author = {Mezher, Rawad and Mansfield, Shane}, month = feb, year = {2022}, note = {arXiv: 2202.04735}, keywords = {Quantum Physics}, } @article{hong_measurement_1987, title = {Measurement of subpicosecond time intervals between two photons by interference}, volume = {59}, url = {https://link.aps.org/doi/10.1103/PhysRevLett.59.2044}, doi = {10.1103/PhysRevLett.59.2044}, abstract = {A fourth-order interference technique has been used to measure the time intervals between two photons, and by implication the length of the photon wave packet, produced in the process of parametric down-conversion. The width of the time-interval distribution, which is largely determined by an interference filter, is found to be about 100 fs, with an accuracy that could, in principle, be less than 1 fs.}, number = {18}, journal = {Physical Review Letters}, author = {Hong, C. K. and Ou, Z. Y. and Mandel, L.}, month = nov, year = {1987}, note = {Publisher: American Physical Society}, pages = {2044--2046}, } @inproceedings{gan_fock_2021, title = {Fock {State}-enhanced {Expressivity} of {Quantum} {Machine} {Learning} {Models}}, copyright = {\&\#169; 2021 The Author(s)}, url = {https://opg.optica.org/abstract.cfm?uri=CLEO_AT-2021-JW1A.73}, doi = {10.1364/CLEO_AT.2021.JW1A.73}, abstract = {We propose quantum classifiers based on encoding classical data onto Fock states using tunable beam-splitter meshes, similar to the boson sampling architecture. We show that higher photon numbers enhance the expressive power of the circuit.}, language = {EN}, booktitle = {Conference on {Lasers} and {Electro}-{Optics} (2021), paper {JW1A}.73}, publisher = {Optica Publishing Group}, author = {Gan, Beng Yee and Leykam, Daniel and Angelakis, Dimitris G. and Angelakis, Dimitris G.}, month = may, year = {2021}, } @book{constantin2020navier, title={Navier-stokes equations}, author={Constantin, Peter and Foias, Ciprian}, year={2020}, publisher={University of Chicago Press} } @book{widder1976heat, title={The heat equation}, author={Widder, David Vernon}, volume={67}, year={1976}, publisher={Academic Press} } @article{schuld2015introduction, title={An introduction to quantum machine learning}, author={Schuld, Maria and Sinayskiy, Ilya and Petruccione, Francesco}, journal={Contemporary Physics}, volume={56}, number={2}, pages={172--185}, year={2015}, publisher={Taylor \& Francis} } @article{nelder1965simplex, title={A simplex method for function minimization}, author={Nelder, John A and Mead, Roger}, journal={The computer journal}, volume={7}, number={4}, pages={308--313}, year={1965}, publisher={Oxford University Press} } @article{brylinski2002universal, title={Universal quantum gates}, author={Brylinski, Jean-Luc and Brylinski, Ranee}, journal={Mathematics of quantum computation}, volume={79}, year={2002} } @article{AB16, title={BosonSampling with lost photons}, author={Aaronson, Scott and Brod, Daniel J}, journal={Physical Review A}, volume={93}, number={1}, pages={012335}, year={2016}, publisher={APS} } @article{Arkhipov15, title={BosonSampling is robust against small errors in the network matrix}, author={Arkhipov, Alex}, journal={Physical Review A}, volume={92}, number={6}, pages={062326}, year={2015}, publisher={APS} } @article{schuld2021effect, title={Effect of data encoding on the expressive power of variational quantum-machine-learning models}, author={Schuld, Maria and Sweke, Ryan and Meyer, Johannes Jakob}, journal={Physical Review A}, volume={103}, number={3}, pages={032430}, year={2021}, publisher={APS} } @article{perez2020data, title={Data re-uploading for a universal quantum classifier}, author={P{\'e}rez-Salinas, Adri{\'a}n and Cervera-Lierta, Alba and Gil-Fuster, Elies and Latorre, Jos{\'e} I}, journal={Quantum}, volume={4}, pages={226}, year={2020}, publisher={Verein zur F{\"o}rderung des Open Access Publizierens in den Quantenwissenschaften} } @book{brualdi1991combinatorial, title={Combinatorial matrix theory}, author={Brualdi, Richard A and Ryser, Herbert John and others}, volume={39}, year={1991}, publisher={Springer} } @article{Haar17, title={Direct dialling of Haar random unitary matrices}, author={Russell, Nicholas J and Chakhmakhchyan, Levon and O’Brien, Jeremy L and Laing, Anthony}, journal={New journal of physics}, volume={19}, number={3}, pages={033007}, year={2017}, publisher={IOP Publishing} } @article{KK14, title={Gaussian noise sensitivity and BosonSampling}, author={Kalai, Gil and Kindler, Guy}, journal={arXiv preprint arXiv:1409.3093}, year={2014} } @article{long2001grover, title={Grover algorithm with zero theoretical failure rate}, author={Long, Gui-Lu}, journal={Physical Review A}, volume={64}, number={2}, pages={022307}, year={2001}, publisher={APS} } @article{roy2022deterministic, title={Deterministic Grover search with a restricted oracle}, author={Roy, Tanay and Jiang, Liang and Schuster, David I}, journal={arXiv preprint arXiv:2201.00091}, year={2022} } @article{kyriienko_solving_2021, title = {Solving nonlinear differential equations with differentiable quantum circuits}, volume = {103}, issn = {2469-9926, 2469-9934}, url = {http://arxiv.org/abs/2011.10395}, doi = {10.1103/PhysRevA.103.052416}, abstract = {We propose a quantum algorithm to solve systems of nonlinear differential equations. Using a quantum feature map encoding, we define functions as expectation values of parametrized quantum circuits. We use automatic differentiation to represent function derivatives in an analytical form as differentiable quantum circuits (DQCs), thus avoiding inaccurate finite difference procedures for calculating gradients. We describe a hybrid quantum-classical workflow where DQCs are trained to satisfy differential equations and specified boundary conditions. As a particular example setting, we show how this approach can implement a spectral method for solving differential equations in a high-dimensional feature space. From a technical perspective, we design a Chebyshev quantum feature map that offers a powerful basis set of fitting polynomials and possesses rich expressivity. We simulate the algorithm to solve an instance of Navier-Stokes equations, and compute density, temperature and velocity profiles for the fluid flow in a convergent-divergent nozzle.}, number = {5}, journal = {Physical Review A}, author = {Kyriienko, Oleksandr and Paine, Annie E. and Elfving, Vincent E.}, month = may, year = {2021}, note = {arXiv: 2011.10395}, keywords = {Quantum Physics, Condensed Matter - Disordered Systems and Neural Networks}, pages = {052416}, } @article{knill_scheme_2001, title = {A scheme for efficient quantum computation with linear optics}, volume = {409}, copyright = {2001 Macmillan Magazines Ltd.}, issn = {1476-4687}, url = {https://www.nature.com/articles/35051009}, doi = {10.1038/35051009}, abstract = {Quantum computers promise to increase greatly the efficiency of solving problems such as factoring large integers, combinatorial optimization and quantum physics simulation. One of the greatest challenges now is to implement the basic quantum-computational elements in a physical system and to demonstrate that they can be reliably and scalably controlled. One of the earliest proposals for quantum computation is based on implementing a quantum bit with two optical modes containing one photon. The proposal is appealing because of the ease with which photon interference can be observed. Until now, it suffered from the requirement for non-linear couplings between optical modes containing few photons. Here we show that efficient quantum computation is possible using only beam splitters, phase shifters, single photon sources and photo-detectors. Our methods exploit feedback from photo-detectors and are robust against errors from photon loss and detector inefficiency. The basic elements are accessible to experimental investigation with current technology.}, language = {en}, number = {6816}, journal = {Nature}, author = {Knill, E. and Laflamme, R. and Milburn, G. J.}, month = jan, year = {2001}, note = {Number: 6816; Publisher: Nature Publishing Group}, keywords = {Humanities and Social Sciences, multidisciplinary, Science}, pages = {46--52}, } @inproceedings{shor_algorithms_1994, title = {Algorithms for quantum computation: discrete logarithms and factoring}, shorttitle = {Algorithms for quantum computation}, doi = {10.1109/SFCS.1994.365700}, abstract = {A computer is generally considered to be a universal computational device; i.e., it is believed able to simulate any physical computational device with a cost in computation time of at most a polynomial factor: It is not clear whether this is still true when quantum mechanics is taken into consideration. Several researchers, starting with David Deutsch, have developed models for quantum mechanical computers and have investigated their computational properties. This paper gives Las Vegas algorithms for finding discrete logarithms and factoring integers on a quantum computer that take a number of steps which is polynomial in the input size, e.g., the number of digits of the integer to be factored. These two problems are generally considered hard on a classical computer and have been used as the basis of several proposed cryptosystems. We thus give the first examples of quantum cryptanalysis.{\textless}{\textgreater}}, booktitle = {Proceedings 35th {Annual} {Symposium} on {Foundations} of {Computer} {Science}}, author = {Shor, P.W.}, month = nov, year = {1994}, keywords = {Circuit simulation, Computational modeling, Computer simulation, Costs, Cryptography, Mechanical factors, Physics computing, Polynomials, Quantum computing, Quantum mechanics}, pages = {124--134}, } @article{wang_boson_2019, title = {Boson {Sampling} with 20 {Input} {Photons} and a 60-{Mode} {Interferometer} in a \$1\{0\}{\textasciicircum}\{14\}\$-{Dimensional} {Hilbert} {Space}}, volume = {123}, url = {https://link.aps.org/doi/10.1103/PhysRevLett.123.250503}, doi = {10.1103/PhysRevLett.123.250503}, abstract = {Quantum computing experiments are moving into a new realm of increasing size and complexity, with the short-term goal of demonstrating an advantage over classical computers. Boson sampling is a promising platform for such a goal; however, the number of detected single photons is up to five so far, limiting these small-scale implementations to a proof-of-principle stage. Here, we develop solid-state sources of highly efficient, pure, and indistinguishable single photons and 3D integration of ultralow-loss optical circuits. We perform experiments with 20 pure single photons fed into a 60-mode interferometer. In the output, we detect up to 14 photons and sample over Hilbert spaces with a size up to 3.7×1014, over 10 orders of magnitude larger than all previous experiments, which for the first time enters into a genuine sampling regime where it becomes impossible to exhaust all possible output combinations. The results are validated against distinguishable samplers and uniform samplers with a confidence level of 99.9\%.}, number = {25}, journal = {Physical Review Letters}, author = {Wang, Hui and Qin, Jian and Ding, Xing and Chen, Ming-Cheng and Chen, Si and You, Xiang and He, Yu-Ming and Jiang, Xiao and You, L. and Wang, Z. and Schneider, C. and Renema, Jelmer J. and Höfling, Sven and Lu, Chao-Yang and Pan, Jian-Wei}, month = dec, year = {2019}, note = {Publisher: American Physical Society}, pages = {250503} } @article{santori2002indistinguishable, title={Indistinguishable photons from a single-photon device}, author={Santori, Charles and Fattal, David and Vu{\v{c}}kovi{\'c}, Jelena and Solomon, Glenn S and Yamamoto, Yoshihisa}, journal={nature}, volume={419}, number={6907}, pages={594--597}, year={2002}, publisher={Nature Publishing Group} } @article{politi_shors_2009, title = {Shor’s {Quantum} {Factoring} {Algorithm} on a {Photonic} {Chip}}, volume = {325}, url = {https://www.science.org/doi/abs/10.1126/science.1173731}, doi = {10.1126/science.1173731}, number = {5945}, journal = {Science}, author = {Politi, Alberto and Matthews, Jonathan C. F. and O'Brien, Jeremy L.}, month = sep, year = {2009}, note = {Publisher: American Association for the Advancement of Science}, pages = {1221--1221} } @article{hilaire_error-correcting_2021, title = {Error-correcting entanglement swapping using a practical logical photon encoding}, volume = {104}, url = {https://link.aps.org/doi/10.1103/PhysRevA.104.052623}, doi = {10.1103/PhysRevA.104.052623}, abstract = {Several emerging quantum technologies, including quantum networks, and modular and fusion-based quantum computing, rely crucially on the ability to perform photonic Bell state measurements. Therefore, photon losses and the 50\% success probablity upper bound of Bell state measurements pose a critical limitation to photonic quantum technologies. Here, we develop protocols that overcome these two key challenges through logical encoding of photonic qubits. Our approach uses a tree graph state logical encoding, which can be produced deterministically with a few quantum emitters, and achieves near-deterministic logical photonic Bell state measurements while also protecting against errors including photon losses, with a record loss-tolerance threshold.}, number = {5}, journal = {Physical Review A}, author = {Hilaire, Paul and Barnes, Edwin and Economou, Sophia E. and Grosshans, Frédéric}, month = nov, year = {2021}, note = {Publisher: American Physical Society}, pages = {052623}, } @article{kieling_percolation_2007, title = {Percolation, {Renormalization}, and {Quantum} {Computing} with {Nondeterministic} {Gates}}, volume = {99}, url = {https://link.aps.org/doi/10.1103/PhysRevLett.99.130501}, doi = {10.1103/PhysRevLett.99.130501}, abstract = {We apply a notion of static renormalization to the preparation of entangled states for quantum computing, exploiting ideas from percolation theory. Such a strategy yields a novel way to cope with the randomness of nondeterministic quantum gates. This is most relevant in the context of optical architectures, where probabilistic gates are common, and cold atoms in optical lattices, where hole defects occur. We demonstrate how to efficiently construct cluster states without the need for rerouting, thereby avoiding a massive amount of conditional dynamics; we furthermore show that except for a single layer of gates during the preparation, all subsequent operations can be shifted to the final adapted single-qubit measurements. Remarkably, cluster state preparation is achieved using essentially the same scaling in resources as if deterministic gates were available.}, number = {13}, journal = {Physical Review Letters}, author = {Kieling, K. and Rudolph, T. and Eisert, J.}, month = sep, year = {2007}, note = {Publisher: American Physical Society}, pages = {130501}, } @article{brooks_beyond_2019, title = {Beyond quantum supremacy: the hunt for useful quantum computers}, volume = {574}, copyright = {2021 Nature}, shorttitle = {Beyond quantum supremacy}, url = {https://www.nature.com/articles/d41586-019-02936-3}, doi = {10.1038/d41586-019-02936-3}, abstract = {The hunt for useful quantum computers.}, language = {en}, number = {7776}, journal = {Nature}, author = {Brooks, Michael}, month = oct, year = {2019}, keywords = {Chemistry, Computer science, Quantum information, Quantum physics}, pages = {19--21}, } @article{arute_quantum_2019, title = {Quantum supremacy using a programmable superconducting processor}, volume = {574}, copyright = {2019 The Author(s), under exclusive licence to Springer Nature Limited}, issn = {1476-4687}, url = {https://www.nature.com/articles/s41586-019-1666-5}, doi = {10.1038/s41586-019-1666-5}, abstract = {The promise of quantum computers is that certain computational tasks might be executed exponentially faster on a quantum processor than on a classical processor1. A fundamental challenge is to build a high-fidelity processor capable of running quantum algorithms in an exponentially large computational space. Here we report the use of a processor with programmable superconducting qubits2–7 to create quantum states on 53 qubits, corresponding to a computational state-space of dimension 253 (about 1016). Measurements from repeated experiments sample the resulting probability distribution, which we verify using classical simulations. Our Sycamore processor takes about 200 seconds to sample one instance of a quantum circuit a million times—our benchmarks currently indicate that the equivalent task for a state-of-the-art classical supercomputer would take approximately 10,000 years. This dramatic increase in speed compared to all known classical algorithms is an experimental realization of quantum supremacy8–14 for this specific computational task, heralding a much-anticipated computing paradigm.}, language = {en}, number = {7779}, journal = {Nature}, author = {Arute, Frank and Arya, Kunal and Babbush, Ryan and Bacon, Dave and Bardin, Joseph C. and Barends, Rami and Biswas, Rupak and Boixo, Sergio and Brandao, Fernando G. S. L. and Buell, David A. and Burkett, Brian and Chen, Yu and Chen, Zijun and Chiaro, Ben and Collins, Roberto and Courtney, William and Dunsworth, Andrew and Farhi, Edward and Foxen, Brooks and Fowler, Austin and Gidney, Craig and Giustina, Marissa and Graff, Rob and Guerin, Keith and Habegger, Steve and Harrigan, Matthew P. and Hartmann, Michael J. and Ho, Alan and Hoffmann, Markus and Huang, Trent and Humble, Travis S. and Isakov, Sergei V. and Jeffrey, Evan and Jiang, Zhang and Kafri, Dvir and Kechedzhi, Kostyantyn and Kelly, Julian and Klimov, Paul V. and Knysh, Sergey and Korotkov, Alexander and Kostritsa, Fedor and Landhuis, David and Lindmark, Mike and Lucero, Erik and Lyakh, Dmitry and Mandrà, Salvatore and McClean, Jarrod R. and McEwen, Matthew and Megrant, Anthony and Mi, Xiao and Michielsen, Kristel and Mohseni, Masoud and Mutus, Josh and Naaman, Ofer and Neeley, Matthew and Neill, Charles and Niu, Murphy Yuezhen and Ostby, Eric and Petukhov, Andre and Platt, John C. and Quintana, Chris and Rieffel, Eleanor G. and Roushan, Pedram and Rubin, Nicholas C. and Sank, Daniel and Satzinger, Kevin J. and Smelyanskiy, Vadim and Sung, Kevin J. and Trevithick, Matthew D. and Vainsencher, Amit and Villalonga, Benjamin and White, Theodore and Yao, Z. Jamie and Yeh, Ping and Zalcman, Adam and Neven, Hartmut and Martinis, John M.}, month = oct, year = {2019}, keywords = {Quantum information, Quantum physics}, pages = {505--510}, } @article{zhong_quantum_2020, title = {Quantum computational advantage using photons}, volume = {370}, url = {https://www.science.org/doi/abs/10.1126/science.abe8770}, doi = {10.1126/science.abe8770}, number = {6523}, journal = {Science}, author = {Zhong, Han-Sen and Wang, Hui and Deng, Yu-Hao and Chen, Ming-Cheng and Peng, Li-Chao and Luo, Yi-Han and Qin, Jian and Wu, Dian and Ding, Xing and Hu, Yi and Hu, Peng and Yang, Xiao-Yan and Zhang, Wei-Jun and Li, Hao and Li, Yuxuan and Jiang, Xiao and Gan, Lin and Yang, Guangwen and You, Lixing and Wang, Zhen and Li, Li and Liu, Nai-Le and Lu, Chao-Yang and Pan, Jian-Wei}, month = dec, year = {2020}, note = {Publisher: American Association for the Advancement of Science}, pages = {1460--1463}, } @article{wu_strong_2021, title = {Strong {Quantum} {Computational} {Advantage} {Using} a {Superconducting} {Quantum} {Processor}}, volume = {127}, url = {https://link.aps.org/doi/10.1103/PhysRevLett.127.180501}, doi = {10.1103/PhysRevLett.127.180501}, abstract = {Scaling up to a large number of qubits with high-precision control is essential in the demonstrations of quantum computational advantage to exponentially outpace the classical hardware and algorithmic improvements. Here, we develop a two-dimensional programmable superconducting quantum processor, Zuchongzhi, which is composed of 66 functional qubits in a tunable coupling architecture. To characterize the performance of the whole system, we perform random quantum circuits sampling for benchmarking, up to a system size of 56 qubits and 20 cycles. The computational cost of the classical simulation of this task is estimated to be 2–3 orders of magnitude higher than the previous work on 53-qubit Sycamore processor [Nature 574, 505 (2019). We estimate that the sampling task finished by Zuchongzhi in about 1.2 h will take the most powerful supercomputer at least 8 yr. Our work establishes an unambiguous quantum computational advantage that is infeasible for classical computation in a reasonable amount of time. The high-precision and programmable quantum computing platform opens a new door to explore novel many-body phenomena and implement complex quantum algorithms.}, number = {18}, journal = {Physical Review Letters}, author = {Wu, Yulin and Bao, Wan-Su and Cao, Sirui and Chen, Fusheng and Chen, Ming-Cheng and Chen, Xiawei and Chung, Tung-Hsun and Deng, Hui and Du, Yajie and Fan, Daojin and Gong, Ming and Guo, Cheng and Guo, Chu and Guo, Shaojun and Han, Lianchen and Hong, Linyin and Huang, He-Liang and Huo, Yong-Heng and Li, Liping and Li, Na and Li, Shaowei and Li, Yuan and Liang, Futian and Lin, Chun and Lin, Jin and Qian, Haoran and Qiao, Dan and Rong, Hao and Su, Hong and Sun, Lihua and Wang, Liangyuan and Wang, Shiyu and Wu, Dachao and Xu, Yu and Yan, Kai and Yang, Weifeng and Yang, Yang and Ye, Yangsen and Yin, Jianghan and Ying, Chong and Yu, Jiale and Zha, Chen and Zhang, Cha and Zhang, Haibin and Zhang, Kaili and Zhang, Yiming and Zhao, Han and Zhao, Youwei and Zhou, Liang and Zhu, Qingling and Lu, Chao-Yang and Peng, Cheng-Zhi and Zhu, Xiaobo and Pan, Jian-Wei}, month = oct, year = {2021}, note = {Publisher: American Physical Society}, pages = {180501}, } @article{zhong_phase-programmable_2021, title = {Phase-{Programmable} {Gaussian} {Boson} {Sampling} {Using} {Stimulated} {Squeezed} {Light}}, volume = {127}, url = {https://link.aps.org/doi/10.1103/PhysRevLett.127.180502}, doi = {10.1103/PhysRevLett.127.180502}, abstract = {We report phase-programmable Gaussian boson sampling (GBS) which produces up to 113 photon detection events out of a 144-mode photonic circuit. A new high-brightness and scalable quantum light source is developed, exploring the idea of stimulated emission of squeezed photons, which has simultaneously near-unity purity and efficiency. This GBS is programmable by tuning the phase of the input squeezed states. The obtained samples are efficiently validated by inferring from computationally friendly subsystems, which rules out hypotheses including distinguishable photons and thermal states. We show that our GBS experiment passes a nonclassicality test based on inequality constraints, and we reveal nontrivial genuine high-order correlations in the GBS samples, which are evidence of robustness against possible classical simulation schemes. This photonic quantum computer, Jiuzhang 2.0, yields a Hilbert space dimension up to ∼1043, and a sampling rate ∼1024 faster than using brute-force simulation on classical supercomputers.}, number = {18}, journal = {Physical Review Letters}, author = {Zhong, Han-Sen and Deng, Yu-Hao and Qin, Jian and Wang, Hui and Chen, Ming-Cheng and Peng, Li-Chao and Luo, Yi-Han and Wu, Dian and Gong, Si-Qiu and Su, Hao and Hu, Yi and Hu, Peng and Yang, Xiao-Yan and Zhang, Wei-Jun and Li, Hao and Li, Yuxuan and Jiang, Xiao and Gan, Lin and Yang, Guangwen and You, Lixing and Wang, Zhen and Li, Li and Liu, Nai-Le and Renema, Jelmer J. and Lu, Chao-Yang and Pan, Jian-Wei}, month = oct, year = {2021}, note = {Publisher: American Physical Society}, pages = {180502}, } @article{farhi_quantum_2014, title = {A {Quantum} {Approximate} {Optimization} {Algorithm}}, url = {http://arxiv.org/abs/1411.4028}, abstract = {We introduce a quantum algorithm that produces approximate solutions for combinatorial optimization problems. The algorithm depends on a positive integer p and the quality of the approximation improves as p is increased. The quantum circuit that implements the algorithm consists of unitary gates whose locality is at most the locality of the objective function whose optimum is sought. The depth of the circuit grows linearly with p times (at worst) the number of constraints. If p is fixed, that is, independent of the input size, the algorithm makes use of efficient classical preprocessing. If p grows with the input size a different strategy is proposed. We study the algorithm as applied to MaxCut on regular graphs and analyze its performance on 2-regular and 3-regular graphs for fixed p. For p = 1, on 3-regular graphs the quantum algorithm always finds a cut that is at least 0.6924 times the size of the optimal cut.}, journal = {arXiv:1411.4028 [quant-ph]}, author = {Farhi, Edward and Goldstone, Jeffrey and Gutmann, Sam}, month = nov, year = {2014}, note = {arXiv: 1411.4028}, keywords = {Quantum Physics}, } @article{bartolucci_fusion-based_2021, title = {Fusion-based quantum computation}, url = {http://arxiv.org/abs/2101.09310}, abstract = {We introduce fusion-based quantum computing (FBQC) - a model of universal quantum computation in which entangling measurements, called fusions, are performed on the qubits of small constant-sized entangled resource states. We introduce a stabilizer formalism for analyzing fault tolerance and computation in these schemes. This framework naturally captures the error structure that arises in certain physical systems for quantum computing, such as photonics. FBQC can offer significant architectural simplifications, enabling hardware made up of many identical modules, requiring an extremely low depth of operations on each physical qubit and reducing classical processing requirements. We present two pedagogical examples of fault-tolerant schemes constructed in this framework and numerically evaluate their threshold under a hardware agnostic fusion error model including both erasure and Pauli error. We also study an error model of linear optical quantum computing with probabilistic fusion and photon loss. In FBQC the non-determinism of fusion is directly dealt with by the quantum error correction protocol, along with other errors. We find that tailoring the fault-tolerance framework to the physical system allows the scheme to have a higher threshold than schemes reported in literature. We present a ballistic scheme which can tolerate a 10.4\% probability of suffering photon loss in each fusion.}, journal = {arXiv:2101.09310 [quant-ph]}, author = {Bartolucci, Sara and Birchall, Patrick and Bombin, Hector and Cable, Hugo and Dawson, Chris and Gimeno-Segovia, Mercedes and Johnston, Eric and Kieling, Konrad and Nickerson, Naomi and Pant, Mihir and Pastawski, Fernando and Rudolph, Terry and Sparrow, Chris}, month = jan, year = {2021}, note = {arXiv: 2101.09310}, keywords = {Quantum Physics}, } @inproceedings{green_quipper_2013, address = {New York, NY, USA}, series = {{PLDI} '13}, title = {Quipper: a scalable quantum programming language}, isbn = {978-1-4503-2014-6}, shorttitle = {Quipper}, url = {https://doi.org/10.1145/2491956.2462177}, doi = {10.1145/2491956.2462177}, abstract = {The field of quantum algorithms is vibrant. Still, there is currently a lack of programming languages for describing quantum computation on a practical scale, i.e., not just at the level of toy problems. We address this issue by introducing Quipper, a scalable, expressive, functional, higher-order quantum programming language. Quipper has been used to program a diverse set of non-trivial quantum algorithms, and can generate quantum gate representations using trillions of gates. It is geared towards a model of computation that uses a classical computer to control a quantum device, but is not dependent on any particular model of quantum hardware. Quipper has proven effective and easy to use, and opens the door towards using formal methods to analyze quantum algorithms.}, booktitle = {Proceedings of the 34th {ACM} {SIGPLAN} {Conference} on {Programming} {Language} {Design} and {Implementation}}, publisher = {Association for Computing Machinery}, author = {Green, Alexander S. and Lumsdaine, Peter LeFanu and Ross, Neil J. and Selinger, Peter and Valiron, Benoît}, month = jun, year = {2013}, keywords = {quantum programming languages, quipper}, pages = {333--342}, } @article{wecker_liqui_2014, title = {{LIQUi}{\textbar}{\textgreater}: {A} {Software} {Design} {Architecture} and {Domain}-{Specific} {Language} for {Quantum} {Computing}}, shorttitle = {{LIQUi}{\textbar}{\textgreater}}, url = {http://arxiv.org/abs/1402.4467}, abstract = {Languages, compilers, and computer-aided design tools will be essential for scalable quantum computing, which promises an exponential leap in our ability to execute complex tasks. LIQUi{\textbar}{\textgreater} is a modular software architecture designed to control quantum hardware. It enables easy programming, compilation, and simulation of quantum algorithms and circuits, and is independent of a specific quantum architecture. LIQUi{\textbar}{\textgreater} contains an embedded, domain-specific language designed for programming quantum algorithms, with F\# as the host language. It also allows the extraction of a circuit data structure that can be used for optimization, rendering, or translation. The circuit can also be exported to external hardware and software environments. Two different simulation environments are available to the user which allow a trade-off between number of qubits and class of operations. LIQUi{\textbar}{\textgreater} has been implemented on a wide range of runtimes as back-ends with a single user front-end. We describe the significant components of the design architecture and how to express any given quantum algorithm.}, journal = {arXiv:1402.4467 [quant-ph]}, author = {Wecker, Dave and Svore, Krysta M.}, month = feb, year = {2014}, note = {arXiv: 1402.4467}, keywords = {Computer Science - Emerging Technologies, Computer Science - Programming Languages, Quantum Physics}, } @article{fingerhuth_open_2018, title = {Open source software in quantum computing}, volume = {13}, issn = {1932-6203}, url = {https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0208561}, doi = {10.1371/journal.pone.0208561}, abstract = {Open source software is becoming crucial in the design and testing of quantum algorithms. Many of the tools are backed by major commercial vendors with the goal to make it easier to develop quantum software: this mirrors how well-funded open machine learning frameworks enabled the development of complex models and their execution on equally complex hardware. We review a wide range of open source software for quantum computing, covering all stages of the quantum toolchain from quantum hardware interfaces through quantum compilers to implementations of quantum algorithms, as well as all quantum computing paradigms, including quantum annealing, and discrete and continuous-variable gate-model quantum computing. The evaluation of each project covers characteristics such as documentation, licence, the choice of programming language, compliance with norms of software engineering, and the culture of the project. We find that while the diversity of projects is mesmerizing, only a few attract external developers and even many commercially backed frameworks have shortcomings in software engineering. Based on these observations, we highlight the best practices that could foster a more active community around quantum computing software that welcomes newcomers to the field, but also ensures high-quality, well-documented code.}, language = {en}, number = {12}, journal = {PLOS ONE}, author = {Fingerhuth, Mark and Babej, Tomáš and Wittek, Peter}, month = dec, year = {2018}, note = {Publisher: Public Library of Science}, keywords = {Computer hardware, Computer software, Open source software, Programming languages, Quantum computing, Qubits, Software engineering, Source code}, pages = {e0208561}, } @article{preskill_quantum_2018, title = {Quantum {Computing} in the {NISQ} era and beyond}, volume = {2}, url = {https://quantum-journal.org/papers/q-2018-08-06-79/}, doi = {10.22331/q-2018-08-06-79}, language = {en-GB}, journal = {Quantum}, author = {Preskill, John}, month = aug, year = {2018}, note = {Publisher: Verein zur Förderung des Open Access Publizierens in den Quantenwissenschaften}, pages = {79}, } @article{nikolopoulos_decision_2016, title = {Decision and function problems based on boson sampling}, volume = {94}, url = {https://link.aps.org/doi/10.1103/PhysRevA.94.012315}, doi = {10.1103/PhysRevA.94.012315}, abstract = {Boson sampling is a mathematical problem that is strongly believed to be intractable for classical computers, whereas passive linear interferometers can produce samples efficiently. So far, the problem remains a computational curiosity, and the possible usefulness of boson-sampling devices is mainly limited to the proof of quantum supremacy. The purpose of this work is to investigate whether boson sampling can be used as a resource of decision and function problems that are computationally hard, and may thus have cryptographic applications. After the definition of a rather general theoretical framework for the design of such problems, we discuss their solution by means of a brute-force numerical approach, as well as by means of nonboson samplers. Moreover, we estimate the sample sizes required for their solution by passive linear interferometers, and it is shown that they are independent of the size of the Hilbert space.}, number = {1}, journal = {Physical Review A}, author = {Nikolopoulos, Georgios M. and Brougham, Thomas}, month = jul, year = {2016}, note = {Publisher: American Physical Society}, pages = {012315}, } @article{nikolopoulos_cryptographic_2019, title = {Cryptographic one-way function based on boson sampling}, volume = {18}, issn = {1573-1332}, url = {https://doi.org/10.1007/s11128-019-2372-9}, doi = {10.1007/s11128-019-2372-9}, abstract = {The quest for practical cryptographic primitives that are robust against quantum computers is of vital importance for the field of cryptography. Among the abundance of different cryptographic primitives one may consider, one-way functions stand out as fundamental building blocks of more complex cryptographic protocols, and they play a central role in modern asymmetric cryptography. We propose a mathematical one-way function, which relies on coarse-grained boson sampling. The evaluation and the inversion of the function are discussed in the context of classical and quantum computers. The present results suggest that the scope and power of boson sampling may go beyond the proof of quantum supremacy and pave the way toward cryptographic applications.}, language = {en}, number = {8}, journal = {Quantum Information Processing}, author = {Nikolopoulos, Georgios M.}, month = jul, year = {2019}, pages = {259}, } @article{banchi_molecular_2020, title = {Molecular docking with {Gaussian} {Boson} {Sampling}}, volume = {6}, issn = {2375-2548}, url = {https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7274809/}, doi = {10.1126/sciadv.aax1950}, abstract = {Photonic quantum devices called Gaussian Boson Samplers can be programmed to predict molecular docking configurations., Gaussian Boson Samplers are photonic quantum devices with the potential to perform intractable tasks for classical systems. As with other near-term quantum technologies, an outstanding challenge is to identify specific problems of practical interest where these devices can prove useful. Here, we show that Gaussian Boson Samplers can be used to predict molecular docking configurations, a central problem for pharmaceutical drug design. We develop an approach where the problem is reduced to finding the maximum weighted clique in a graph, and show that Gaussian Boson Samplers can be programmed to sample large-weight cliques, i.e., stable docking configurations, with high probability, even with photon losses. We also describe how outputs from the device can be used to enhance the performance of classical algorithms. To benchmark our approach, we predict the binding mode of a ligand to the tumor necrosis factor-α converting enzyme, a target linked to immune system diseases and cancer.}, number = {23}, journal = {Science Advances}, author = {Banchi, Leonardo and Fingerhuth, Mark and Babej, Tomas and Ing, Christopher and Arrazola, Juan Miguel}, month = jun, year = {2020}, pmid = {32548251}, pmcid = {PMC7274809}, pages = {eaax1950}, } @article{noauthor_generating_2012, title = {Generating, manipulating and measuring entanglement and mixture with a reconfigurable photonic circuit}, author={Shadbolt, Peter J and Verde, Maria R and Peruzzo, Alberto and Politi, Alberto and Laing, Anthony and Lobino, Mirko and Matthews, Jonathan CF and Thompson, Mark G and O'Brien, Jeremy L}, volume = {6}, issn = {1749-4893}, url = {https://doi.org/10.1038/nphoton.2011.283}, doi = {10.1038/nphoton.2011.283}, abstract = {Entanglement is the quintessential quantum-mechanical phenomenon understood to lie at the heart of future quantum technologies and the subject of fundamental scientific investigations. Mixture, resulting from noise, is often an unwanted result of interaction with an environment, but is also of fundamental interest, and is proposed to play a role in some biological processes. Here, we report an integrated waveguide device that can generate and completely characterize pure two-photon states with any amount of entanglement and arbitrary single-photon states with any amount of mixture. The device consists of a reconfigurable integrated quantum photonic circuit with eight voltage-controlled phase shifters. We demonstrate that, for thousands of randomly chosen configurations, the device performs with high fidelity. We generate maximally and non-maximally entangled states, violate a Bell-type inequality with a continuum of partially entangled states, and demonstrate the generation of arbitrary one-qubit mixed states.}, number = {1}, journal = {Nature Photonics}, month = jan, year = {2012}, pages = {45--49}, } @article{VQE2, title = {Scalable Quantum Simulation of Molecular Energies}, author = {O'Malley, P. J. J. and Babbush, R. and Kivlichan, I. D. and Romero, J. and McClean, J. R. and Barends, R. and Kelly, J. and Roushan, P. and Tranter, A. and Ding, N. and Campbell, B. and Chen, Y. and Chen, Z. and Chiaro, B. and Dunsworth, A. and Fowler, A. G. and Jeffrey, E. and Lucero, E. and Megrant, A. and Mutus, J. Y. and Neeley, M. and Neill, C. and Quintana, C. and Sank, D. and Vainsencher, A. and Wenner, J. and White, T. C. and Coveney, P. V. and Love, P. J. and Neven, H. and Aspuru-Guzik, A. and Martinis, J. M.}, journal = {Phys. Rev. X}, volume = {6}, issue = {3}, pages = {031007}, numpages = {13}, year = {2016}, month = {Jul}, publisher = {American Physical Society}, doi = {10.1103/PhysRevX.6.031007}, url = {https://link.aps.org/doi/10.1103/PhysRevX.6.031007} } @article{CRO+2019Quantum, annote = {doi: 10.1021/acs.chemrev.8b00803}, author = {Cao, Yudong and Romero, Jonathan and Olson, Jonathan P. and Degroote, Matthias and Johnson, Peter D. and Kieferov{\'a}, M{\'a}ria and Kivlichan, Ian D. and Menke, Tim and Peropadre, Borja and Sawaya, Nicolas P. D. and Sim, Sukin and Veis, Libor and Aspuru-Guzik, Al{\'a}n}, booktitle = {Chemical Reviews}, da = {2019/10/09}, date = {2019/10/09}, date-added = {2022-03-28 16:31:52 +0200}, date-modified = {2022-03-28 16:31:52 +0200}, doi = {10.1021/acs.chemrev.8b00803}, isbn = {0009-2665}, journal = {Chemical Reviews}, journal1 = {Chem. Rev.}, m3 = {doi: 10.1021/acs.chemrev.8b00803}, month = {10}, number = {19}, pages = {10856--10915}, publisher = {American Chemical Society}, title = {Quantum Chemistry in the Age of Quantum Computing}, ty = {JOUR}, url = {https://doi.org/10.1021/acs.chemrev.8b00803}, volume = {119}, year = {2019}, year1 = {2019}, Bdsk-Url-1 = {https://doi.org/10.1021/acs.chemrev.8b00803} } @article{MEA+2020Quantum, title = {Quantum computational chemistry}, author = {McArdle, Sam and Endo, Suguru and Aspuru-Guzik, Al\'an and Benjamin, Simon C. and Yuan, Xiao}, journal = {Rev. Mod. Phys.}, volume = {92}, issue = {1}, pages = {015003}, numpages = {51}, year = {2020}, month = {Mar}, publisher = {American Physical Society}, doi = {10.1103/RevModPhys.92.015003}, url = {https://link.aps.org/doi/10.1103/RevModPhys.92.015003} } @misc{GGM+2021QOptCraft, doi = {10.48550/ARXIV.2108.06186}, url = {https://arxiv.org/abs/2108.06186}, author = {Aguado, Daniel Gómez and Gimeno, Vicent and Moyano-Fernández, Julio José and Garcia-Escartin, Juan Carlos}, keywords = {Quantum Physics (quant-ph), FOS: Physical sciences, FOS: Physical sciences, 81-08 (Primary) 81-10, 68Q12 (Secondary)}, title = {QOptCraft: A Python package for the design and study of linear optical quantum systems}, publisher = {arXiv}, year = {2021}, copyright = {arXiv.org perpetual, non-exclusive license} } @misc{ZHB+2020Adaptive, doi = {10.48550/ARXIV.2005.10258}, url = {https://arxiv.org/abs/2005.10258}, author = {Zhu, Linghua and Tang, Ho Lun and Barron, George S. and Calderon-Vargas, F. A. and Mayhall, Nicholas J. and Barnes, Edwin and Economou, Sophia E.}, keywords = {Quantum Physics (quant-ph), FOS: Physical sciences, FOS: Physical sciences}, title = {An adaptive quantum approximate optimization algorithm for solving combinatorial problems on a quantum computer}, publisher = {arXiv}, year = {2020}, copyright = {arXiv.org perpetual, non-exclusive license} } @misc{Preskill2011Quantum, doi = {arXiv:1203.5813}, url = {https://arxiv.org/abs/1203.5813}, author = {Preskill, John}, title = {Quantum computing and the entanglement frontier}, publisher = {arXiv}, year = {2011}, copyright = {arXiv.org perpetual, non-exclusive license} } @article{glynn2010permanent, title = {The permanent of a square matrix}, author = {Glynn, David G}, journal = {European Journal of Combinatorics}, volume = {31}, number = {7}, pages = {1887--1891}, year = {2010}, publisher = {Elsevier} } @article{heurtel2022, title = {Strong Simulation of Linear Optical Processes}, author = {Heurtel, Nicolas and Mansfield, Shane and Senellart, Jean and Valiron, Benoit}, journal = {Computer Physics Communications}, volume = {291}, numpages = {26}, year = {2022}, doi = {10.1016/j.cpc.2023.108848}, url = {https://arxiv.org/abs/2206.10549}, } @article{BCK+2022NISQ, title = {Noisy intermediate-scale quantum algorithms}, author = {Bharti, Kishor and Cervera-Lierta, Alba and Kyaw, Thi Ha and Haug, Tobias and Alperin-Lea, Sumner and Anand, Abhinav and Degroote, Matthias and Heimonen, Hermanni and Kottmann, Jakob S. and Menke, Tim and Mok, Wai-Keong and Sim, Sukin and Kwek, Leong-Chuan and Aspuru-Guzik, Al\'an}, journal = {Rev. Mod. Phys.}, volume = {94}, issue = {1}, pages = {015004}, numpages = {69}, year = {2022}, month = {Feb}, publisher = {American Physical Society}, doi = {10.1103/RevModPhys.94.015004}, url = {https://link.aps.org/doi/10.1103/RevModPhys.94.015004} } @book{ryser1963combinatorial, title = {Combinatorial mathematics}, author = {Ryser, Herbert John}, volume = {14}, year = {1963}, publisher = {American Mathematical Soc.} } @article{gupt2019walrus, title = {The Walrus: a library for the calculation of hafnians, Hermite polynomials and Gaussian boson sampling}, author = {Gupt, Brajesh and Izaac, Josh and Quesada, Nicol{\'a}s}, journal = {Journal of Open Source Software}, volume = {4}, number = {44}, pages = {1705}, year = {2019} } @phdthesis{giesz2015cavity, title = {Cavity-enhanced Photon-Photon Interactions With Bright Quantum Dot Sources}, author = {Giesz, Val{\'e}rian}, year = {2015}, school = {Universit{\'e} Paris-Saclay (ComUE)} } @article{VGS+2020Applying, title = {Applying the Quantum Approximate Optimization Algorithm to the Tail-Assignment Problem}, author = {Vikst\aa{}l, Pontus and Gr\"onkvist, Mattias and Svensson, Marika and Andersson, Martin and Johansson, G\"oran and Ferrini, Giulia}, journal = {Phys. Rev. Applied}, volume = {14}, issue = {3}, pages = {034009}, numpages = {12}, year = {2020}, month = {Sep}, publisher = {American Physical Society}, doi = {10.1103/PhysRevApplied.14.034009}, url = {https://link.aps.org/doi/10.1103/PhysRevApplied.14.034009} } @article{JSK+2018Quantum, title = {Quantum Algorithms to Simulate Many-Body Physics of Correlated Fermions}, author = {Jiang, Zhang and Sung, Kevin J. and Kechedzhi, Kostyantyn and Smelyanskiy, Vadim N. and Boixo, Sergio}, journal = {Phys. Rev. Applied}, volume = {9}, issue = {4}, pages = {044036}, numpages = {23}, year = {2018}, month = {Apr}, publisher = {American Physical Society}, doi = {10.1103/PhysRevApplied.9.044036}, url = {https://link.aps.org/doi/10.1103/PhysRevApplied.9.044036} } @article{DHM+2020Towards, title = {Towards analog quantum simulations of lattice gauge theories with trapped ions}, author = {Davoudi, Zohreh and Hafezi, Mohammad and Monroe, Christopher and Pagano, Guido and Seif, Alireza and Shaw, Andrew}, journal = {Phys. Rev. Research}, volume = {2}, issue = {2}, pages = {023015}, numpages = {24}, year = {2020}, month = {Apr}, publisher = {American Physical Society}, doi = {10.1103/PhysRevResearch.2.023015}, url = {https://link.aps.org/doi/10.1103/PhysRevResearch.2.023015} } @article{SBI+2020Measuring, title = {Measuring the similarity of graphs with a {G}aussian {B}oson sampler}, author = {Schuld, Maria and Br\'adler, Kamil and Israel, Robert and Su, Daiqin and Gupt, Brajesh}, journal = {Phys. Rev. A}, volume = {101}, issue = {3}, pages = {032314}, numpages = {11}, year = {2020}, month = {Mar}, publisher = {American Physical Society}, doi = {10.1103/PhysRevA.101.032314}, url = {https://link.aps.org/doi/10.1103/PhysRevA.101.032314} } @misc{HBC+2021Quantum, doi = {10.48550/ARXIV.2112.00778}, url = {https://arxiv.org/abs/2112.00778}, author = {Huang, Hsin-Yuan and Broughton, Michael and Cotler, Jordan and Chen, Sitan and Li, Jerry and Mohseni, Masoud and Neven, Hartmut and Babbush, Ryan and Kueng, Richard and Preskill, John and McClean, Jarrod R.}, keywords = {Quantum Physics (quant-ph), Information Theory (cs.IT), Machine Learning (cs.LG), FOS: Physical sciences, FOS: Physical sciences, FOS: Computer and information sciences, FOS: Computer and information sciences}, title = {Quantum advantage in learning from experiments}, publisher = {arXiv}, year = {2021}, copyright = {Creative Commons Attribution 4.0 International} } @inproceedings{spedalieri_linear_2005, title = {Linear {Optical} {Quantum} {Computing} with {Polarization} {Encoding}}, copyright = {\&\#169; 2005 Optical Society of America}, url = {https://opg.optica.org/abstract.cfm?uri=LS-2005-LMB4}, doi = {10.1364/LS.2005.LMB4}, abstract = {We demonstrate how to implement the KLM scheme for linear optical quantum computing using polarization encoding. The resulting gates have very high fidelity and allow construction of optical cluster states with currently available detectors.}, language = {EN}, booktitle = {Frontiers in {Optics} (2005), paper {LMB4}}, publisher = {Optica Publishing Group}, author = {Spedalieri, Federico and Lee, Hwang and Lee, Hwang and Dowling, Jonathan and Dowling, Jonathan}, month = oct, year = {2005}, pages = {LMB4}, } @article{KMN+2007Linear, title = {Linear optical quantum computing with photonic qubits}, author = {Kok, Pieter and Munro, W. J. and Nemoto, Kae and Ralph, T. C. and Dowling, Jonathan P. and Milburn, G. J.}, journal = {Rev. Mod. Phys.}, volume = {79}, issue = {1}, pages = {135--174}, numpages = {0}, year = {2007}, month = {Jan}, publisher = {American Physical Society}, doi = {10.1103/RevModPhys.79.135}, url = {https://link.aps.org/doi/10.1103/RevModPhys.79.135} } @book{KL2010Introduction, place = {Cambridge}, title = {Introduction to Optical Quantum Information Processing}, DOI = {10.1017/CBO9781139193658}, publisher = {Cambridge University Press}, author = {Kok, Pieter and Lovett, Brendon W.}, year = {2010} } @article{fldzhyan2020optimal, title = {Optimal design of error-tolerant reprogrammable multiport interferometers}, author = {Fldzhyan, Suren A and Saygin, M Yu and Kulik, Sergei P}, journal = {Optics Letters}, volume = {45}, number = {9}, pages = {2632--2635}, year = {2020}, publisher = {Optical Society of America} } @article{clements2016optimal, title = {Optimal design for universal multiport interferometers}, author = {Clements, William R and Humphreys, Peter C and Metcalf, Benjamin J and Kolthammer, W Steven and Walmsley, Ian A}, journal = {Optica}, volume = {3}, number = {12}, pages = {1460--1465}, year = {2016}, publisher = {Optical Society of America} } @article{reck1994experimental, title = {Experimental realization of any discrete unitary operator}, author = {Reck, Michael and Zeilinger, Anton and Bernstein, Herbert J and Bertani, Philip}, journal = {Physical review letters}, volume = {73}, number = {1}, pages = {58}, year = {1994}, publisher = {APS} } @article{PhysRevLett.70.1895, title = {Teleporting an unknown quantum state via dual classical and Einstein-Podolsky-Rosen channels}, author = {Bennett, Charles H. and Brassard, Gilles and Cr\'epeau, Claude and Jozsa, Richard and Peres, Asher and Wootters, William K.}, journal = {Phys. Rev. Lett.}, volume = {70}, issue = {13}, pages = {1895--1899}, numpages = {0}, year = {1993}, month = {Mar}, publisher = {American Physical Society}, doi = {10.1103/PhysRevLett.70.1895}, url = {https://link.aps.org/doi/10.1103/PhysRevLett.70.1895} } @article{lysaght2024quantumcircuitcompressionusing, title = {Quantum circuit compression using qubit logic on qudits}, author = {Liam Lysaght and Timothée Goubault and Patrick Sinnott and Shane Mansfield and Pierre-Emmanuel Emeriau}, journal = {arXiv preprint arXiv:2411.03878}, year = {2024}, eprint = {2411.03878}, archivePrefix = {arXiv}, primaryClass = {quant-ph}, url = {https://arxiv.org/abs/2411.03878}, } @article{gurvits2002, title = {A Deterministic Algorithm for Approximating the Mixed Discriminant and Mixed Volume, and a Combinatorial Corollary}, author = {Gurvits, L. and Samorodnitsky, A.}, journal = {Discrete & Computational Geometry}, volume = {27}, issue = {4}, year = {2002}, pages = {531--550}, doi = {10.1007/s00454-001-0083-2}, url = {https://www.researchgate.net/publication/220453384_A_Deterministic_Algorithm_for_Approximating_the_Mixed_Discriminant_and_Mixed_Volume_and_a_Combinatorial_Corollary}, } @article{goubault2025, title = {Fast and memory efficient strong simulation of noisy adaptive linear optical circuits}, author = {Goubault de Brugière, Timothée and Heurtel, Nicolas}, journal = {arXiv preprint arXiv:2503.05699}, year = {2025}, doi = {10.48550/arXiv.2503.05699}, primaryClass = {quant-ph}, url = {https://arxiv.org/abs/2503.05699}, } @article{mills2024, title = {Mitigating photon loss in linear optical quantum circuits: classical postprocessing methods outperforming postselection}, author = {Mills, James and Mezher, Rawad}, journal = {arXiv preprint arXiv:2405.02278}, year = {2024}, doi = {10.48550/arXiv.2405.02278}, primaryClass = {quant-ph}, url = {https://arxiv.org/abs/2405.02278}, } ================================================ FILE: docs/source/tutorial_advanced.rst ================================================ Advanced tutorials ^^^^^^^^^^^^^^^^^^ .. toctree:: :maxdepth: 2 notebooks/Quantum_teleportation_feed_forward notebooks/Advanced_state_tutorial notebooks/Encoding_Tutorial notebooks/Graph_States_Tutorial notebooks/Simulation_non-unitary_components notebooks/VQA_Tutorial ================================================ FILE: docs/source/tutorial_beginner.rst ================================================ Beginner tutorials ^^^^^^^^^^^^^^^^^^ .. toctree:: :maxdepth: 2 notebooks/State_Tutorial notebooks/Circuit_Tutorial backends notebooks/Computation_Tutorial notebooks/Remote_Computation_Tutorial ================================================ FILE: docs/source/tutorial_expert.rst ================================================ Expert tutorials ^^^^^^^^^^^^^^^^ .. toctree:: :maxdepth: 2 notebooks/Tomography_walkthrough notebooks/QLOQ_QUBO_tutorial notebooks/Density_matrix_Fock_space ================================================ FILE: perceval/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """ Through a simple object-oriented python API, Perceval provides tools for building a circuit with linear optics components, defining single-photon sources and their error model, manipulating Fock states, running simulations, reproducing published experimental papers results and experimenting a new generation of quantum algorithms. It is interfaced with the available QPUs on https://cloud.quandela.com, so it is possible to run computations on an actual photonic computer. Perceval aims to be a companion tool for developing discrete-variable photonic circuits - while simulating their design, modeling their ideal and real-life behaviour; - and proposing a normalized interface to control photonic quantum computers; - while using powerful simulation backends to get state-of-the-art simulation; - and also allowing direct access to the QPUs of Quandela. See also: - Perceval user documentation: https://perceval.quandela.net/docs/ - Quandela cloud documentation: https://cloud.quandela.com/webide/documentation (requires a free account to access) - Perceval bridge with other quantum frameworks: https://github.com/quandela/Perceval_Interop """ from .utils import PMetadata __version__ = PMetadata.version() from .providers import * from .components import * from .backends import * from .utils import * from .rendering import * from .runtime import * from .error_mitigation import photon_recycling from .simulators import Simulator, SimulatorFactory, FFSimulator, NoisySamplingSimulator, Stepper get_logger().info(f"=== Starting Perceval session - process ID: {payload_generator.__process_id__} ===", logging.channel.general) ================================================ FILE: perceval/algorithm/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .sampler import Sampler from .analyzer import Analyzer from .tomography import ProcessTomography, StateTomography, ProcessTomographyMLE, StateTomographyMLE, AProcessTomography ================================================ FILE: perceval/algorithm/abstract_algorithm.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components.abstract_processor import AProcessor class AAlgorithm: _MAX_SHOTS_NAMED_PARAM = "max_shots_per_call" def __init__(self, processor: AProcessor, **kwargs): self._processor = processor if not self._check_compatibility(): raise RuntimeError("Processor and algorithm are not compatible") self.default_job_name = None self._max_shots = kwargs.get(self._MAX_SHOTS_NAMED_PARAM) if self._max_shots: self._max_shots = int(self._max_shots) if self._max_shots < 1: raise RuntimeError(f'`{self._MAX_SHOTS_NAMED_PARAM}` must be a positive value') # max_shots_per_call must be found in **kwargs when the processor is remote. # This condition is forced because the user will consume credits on the cloud and needs to set an upper bound if processor.is_remote and not self._max_shots: raise RuntimeError(f'Please input a `{self._MAX_SHOTS_NAMED_PARAM}` value when using a RemoteProcessor') def _check_compatibility(self) -> bool: # if self._processor.is_remote: # # TODO remote compatibility check # return False return True ================================================ FILE: perceval/algorithm/analyzer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np from .abstract_algorithm import AAlgorithm from .sampler import Sampler from perceval.utils import BasicState, allstate_iterator, Matrix from perceval.components import AProcessor class Analyzer(AAlgorithm): """ Analyses a set of input states vs output states probabilities. :param processor: the processor to analyse :param input_states: list of BasicStates or a mapping {BasicState: name} :param output_states: list of output states. Valid values are: * None (then, the input states are taken as output states) * a list of BasicState * a mapping {BasicState: name} * the string "*" meaning oll possible target states are generated :param mapping: optional mapping {BasicState: name} used for display :param kwargs: as the Analyzer internally uses a Sampler instance, it needs a "max_shots_per_call" value """ def __init__(self, processor: AProcessor, input_states: list[BasicState] | dict[BasicState, str], output_states=None, mapping=None, **kwargs): if mapping is None: mapping = {} super().__init__(processor, **kwargs) self._sampler = Sampler(processor, **kwargs) self._mapping = mapping self.performance = None self.error_rate = None self.fidelity = None self._distribution = None self.default_job_name = 'analyzer' # Enrich mapping and create self.input_state_list if isinstance(input_states, dict): self.input_states_list = [] for k, v in input_states.items(): self._mapping[k] = v self.input_states_list.append(k) elif isinstance(input_states, list): self.input_states_list = input_states else: raise TypeError("input_states must be a list or a dictionary") # Test input_states_list for input_state in self.input_states_list: assert isinstance(input_state, BasicState), "input_states should contain BasicStates" assert input_state.m == self._processor.m, "Incorrect BasicState size" if output_states is None: self.output_states_list = self.input_states_list elif isinstance(output_states, list): self.output_states_list = output_states elif isinstance(output_states, dict): self.output_states_list = [] for k, v in output_states.items(): self._mapping[k] = v self.output_states_list.append(k) elif output_states == '*': out_set = set() for input_state in self.input_states_list: for os in allstate_iterator(input_state): out_set.add(os) self.output_states_list = list(out_set) # All states will be used in compute() # Setup output state selection on detected photons if output_states == '*': min_output_photon_count = 1 # To retrieve all non-empty states on a QPU, set filter to 1 else: min_output_photon_count = processor.m for ostate in self.output_states_list: min_output_photon_count = min(ostate.n, min_output_photon_count) processor.min_detected_photons_filter(min_output_photon_count) def compute(self, normalize: bool = False, expected: dict = None, progress_callback=None): """ Iterate through the input states, generate (post-selected) output states and calculate distance with expected, if provided. :param normalize: whether to normalize the output states :param expected: optional mapping between states in ideal case :param progress_callback: optional callback to inform the user of the task progress """ probs_res = {} logical_perf = [] has_an_empty_PD = False if expected is not None: normalize = True self.error_rate = 0 # Compute probabilities for all input states for idx, i_state in enumerate(self.input_states_list): self._processor.with_input(i_state) job = self._sampler.probs job.name = f'{self.default_job_name} {idx+1}/{len(self.input_states_list)}' probs_output = job.execute_sync() probs = probs_output['results'] if len(probs) == 0: has_an_empty_PD = True probs_res[i_state] = probs if 'logical_perf' in probs_output: logical_perf.append(probs_output['logical_perf']) else: logical_perf.append(probs_output['global_perf']) if progress_callback is not None: progress_callback((idx+1)/len(self.input_states_list)) # Create a distribution matrix and compute performance / error rate if needed self._distribution = Matrix(np.zeros((len(self.input_states_list), len(self.output_states_list)))) for iidx, i_state in enumerate(self.input_states_list): sum_p = 0 for oidx, o_state in enumerate(self.output_states_list): if o_state in probs_res[i_state]: self._distribution[iidx, oidx] = probs_res[i_state][o_state] sum_p += probs_res[i_state][o_state] if expected is not None: expected_o = None if i_state in expected: expected_o = expected[i_state] elif i_state in self._mapping and self._mapping[i_state] in expected: expected_o = expected[self._mapping[i_state]] if not isinstance(expected_o, BasicState): for k, v in self._mapping.items(): if v == expected_o: expected_o = k break if expected_o is None: raise ValueError(f"Output not found in expected mapping for input state: {i_state}") if sum_p > 0: self.error_rate += 1 - \ (self._distribution[iidx, self.output_states_list.index(expected_o)]/sum_p).real if normalize and sum_p != 0: self._distribution[iidx, :] /= sum_p self.performance = min(logical_perf) output = {'results': self._distribution, 'input_states': self.input_states_list, 'output_states': self.output_states_list, 'performance': self.performance} if has_an_empty_PD: output['performance'] = 0 if expected is not None: if has_an_empty_PD: output['error_rate'] = None output['fidelity'] = None else: self.error_rate /= len(self.input_states_list) output['error_rate'] = self.error_rate self.fidelity = 1 - self.error_rate output['fidelity'] = self.fidelity return output @property def distribution(self) -> Matrix: """Return the truth table of the analysis. Computes it if wasn't performed beforehand. :return: a matrix containing the probabilities for each input vs output states """ if self._distribution is None: self.compute() return self._distribution def col(self, output_state: BasicState) -> int | None: """ Return the column number for a given output state in the distribution matrix :param output_state: any computed output state :return: the column number, or None if the output state is unknown """ if output_state in self.output_states_list: return self.output_states_list.index(output_state) return None ================================================ FILE: perceval/algorithm/sampler.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from numbers import Number from .abstract_algorithm import AAlgorithm from perceval.utils import samples_to_sample_count, samples_to_probs, sample_count_to_samples, \ sample_count_to_probs, probs_to_samples, probs_to_sample_count from perceval.utils.logging import get_logger, channel from perceval.components.abstract_processor import AProcessor from perceval.runtime import Job, RemoteJob, LocalJob from perceval.utils import BasicState, NoiseModel class Sampler(AAlgorithm): """ Basic algorithm able to retrieve some sampling data via 3 main methods. * samples(max_samples): returns a list of sampled states * sample_count(max_samples): return a mapping {(output state) : (sampled count)} * probs(): returns a probability distribution of output states The form of the output for all 3 sampling methods is a dictionary containing a 'results' key and several performance values. The Sampler also supports batch jobs, where several computations can be stored in a single job. :param processor: the processor to sample on :param kwargs: when the processor is remote, a "max_shots_per_call" should be passed in order to limit the shots usage. """ PROBS_SIMU_SAMPLE_COUNT = 10000 # Arbitrary value # Absolute maximum of samples for local sampling simulation (should be thresholded by a max_shot value) SAMPLES_MAX_COUNT = int(1e8) _METHOD_MAPPING = { 'probs': {'sample_count': sample_count_to_probs, 'samples': samples_to_probs}, 'sample_count': {'probs': probs_to_sample_count, 'samples': samples_to_sample_count}, 'samples': {'probs': probs_to_samples, 'sample_count': sample_count_to_samples} } _iterator_type_check: dict[str, type] = {'circuit_params': dict, 'input_state': BasicState, 'min_detected_photons': int, 'max_samples': int, 'max_shots': int, 'noise': NoiseModel} def __init__(self, processor: AProcessor, **kwargs): super().__init__(processor, **kwargs) self._iterator = [] self._max_samples = None def _get_primitive_converter(self, method: str): available_primitives = self._processor.available_commands if method in available_primitives: return method, None if method in self._METHOD_MAPPING: pmap = self._METHOD_MAPPING[method] for k, converter in pmap.items(): if k in available_primitives: return k, converter return None, None def _input_available(self) -> bool: if self._processor.input_state is not None: # Default input will cover all cases return True elif len(self._iterator) == 0: # ...else you need at least one iteration... return False else: for it in self._iterator: # ...and all iterations must contain an input state if 'input_state' not in it: return False return True def _check_sample_shot_iterator(self) -> bool: return all("max_samples" in it or "max_shots" in it for it in self._iterator) # Job creation methods def _create_job(self, method: str): assert self._input_available(), "Missing input state" primitive, converter = self._get_primitive_converter(method) if primitive is None: raise RuntimeError( f"cannot find a compatible primitive to execute {method} in {self._processor.available_commands}") method_is_probs = (method.find('sample') == -1) primitive_is_probs = (primitive.find('sample') == -1) delta_parameters = {"command": {}, "mapping": {}} # adapt the parameters list command_param_names = [] if primitive_is_probs else ['max_samples'] if not method_is_probs and primitive_is_probs: delta_parameters["mapping"]['max_samples'] = None # Is to be filled by job._handle_params delta_parameters["mapping"]['max_shots'] = self._max_shots elif method_is_probs and not primitive_is_probs: delta_parameters["command"]['max_samples'] = self.PROBS_SIMU_SAMPLE_COUNT elif not method_is_probs and not primitive_is_probs: delta_parameters["command"]['max_samples'] = None # Is to be filled by job._handle_params if self._processor.is_remote: job_context = None if converter: job_context = {"result_mapping": ['perceval.utils', converter.__name__]} payload = self._processor.prepare_job_payload(primitive) if self._iterator: payload['payload']['iterator'] = self._iterator payload['payload']['max_shots'] = self._max_shots job_name = self.default_job_name if self.default_job_name is not None else method job = RemoteJob(payload, self._processor.get_rpc_handler(), job_name, command_param_names=command_param_names, delta_parameters=delta_parameters, job_context=job_context) get_logger().info( f"Prepare remote job (command: {primitive} on {payload['platform_name']})", channel.general) return job else: func_name = f"_{primitive}_iterate_locally" if self._iterator else f"_{primitive}_wrapper" get_logger().info(f"Prepare local job (command: Sampler.{func_name})", channel.general) return LocalJob(getattr(self, func_name), result_mapping_function=converter, command_param_names=command_param_names, delta_parameters=delta_parameters) @property def samples(self) -> Job: """Create a sample stream job :return: A job where chronologically ordered samples are expected as result """ return self._create_job("samples") @property def sample_count(self) -> Job: """Create a sample count job :return: A job where sample counts are expected as result """ return self._create_job("sample_count") @property def probs(self) -> Job: """Create a sample count job :return: A job where sample counts are expected as result """ return self._create_job("probs") # Iterator construction methods def _add_iteration(self, iter_params): self._check_iteration(iter_params) self._iterator.append(iter_params) def _check_iteration(self, iter_params): assert isinstance(iter_params, dict), "Iteration parameters must be a valid dictionary" for key, val in iter_params.items(): if key in self._iterator_type_check: correct_type = self._iterator_type_check[key] assert isinstance(val, correct_type), \ (f"Iteration: unexpected type for {key}, expected {correct_type.__name__}," f" received {type(val).__name__}") else: raise NotImplementedError(f"Iteration: received unknown key {key}") # Further checks if key == 'circuit_params': for param_name, param_value in val.items(): assert isinstance(param_value, Number), \ f"Iteration: circuit parameters have to be numerical values (got {param_value})" assert param_name in self._processor.get_circuit_parameters(), \ f"Iteration: circuit parameter {param_name} does not exist in processor" elif key == 'input_state': assert val.m == self._processor.m, \ f"Iteration: input state and processor size mismatch (processor size is {self._processor.m})" self._processor.check_input(iter_params['input_state']) def add_iteration(self, **kwargs): """ Add a single iteration to future jobs. :param kwargs: List of accepted keywords: - circuit_params: dict containing pairs (parameter_name: str - value : number) - input_state: BasicState - min_detected_photons: int - max_samples: int - max_shots: int - noise: NoiseModel """ get_logger().info("Add 1 iteration to Sampler", channel.general) self._add_iteration(kwargs) def add_iteration_list(self, iterations: list[dict]): """ Add multiple iterations to future jobs. """ get_logger().info(f"Add {len(iterations)} iterations to Sampler", channel.general) for iter_params in iterations: self._add_iteration(iter_params) def clear_iterations(self): """ Clear all prepared iterations. """ get_logger().info(f"Clear all iterations in Sampler", channel.general) self._iterator = [] @property def n_iterations(self): return len(self._iterator) def _probs_wrapper(self, progress_callback: callable = None): # max_shots is used as the invert of the precision set in the probs computation # Rationale: mimic the fact that the more shots, the more accurate probability distributions are. precision = None if self._max_shots is None else min(1e-6, 1 / self._max_shots) return self._processor.probs(precision, progress_callback) def _samples_wrapper(self, max_samples: int = None, progress_callback: callable = None): if max_samples is None and self._max_shots is None: raise RuntimeError("Local sampling simulation requires max_samples and/or max_shots parameters") if max_samples is None: max_samples = self.SAMPLES_MAX_COUNT return self._processor.samples(max_samples, self._max_shots, progress_callback) # Local iteration methods mimic remote iterations for interchangeability purpose def _probs_iterate_locally(self, max_shots: int = None, progress_callback: callable = None): self._max_shots = max_shots default_it = self._it_default_parameters() results = {'results_list': []} for idx, it in enumerate(self._iterator): self._apply_iteration(default_it | it) precision = None if self._max_shots is None else min(1e-6, 1 / self._max_shots) results['results_list'].append(self._processor.probs(precision)) results['results_list'][-1]['iteration'] = it if progress_callback is not None: progress_callback((idx + 1) / len(self._iterator)) self._apply_iteration(default_it) return results def _samples_iterate_locally(self, max_shots: int = None, max_samples: int = None, progress_callback: callable = None): if max_samples is None and max_shots is None: if not self._check_sample_shot_iterator(): raise RuntimeError("Local sampling simulation requires max_samples and/or max_shots parameters") if max_samples is None: max_samples = self.SAMPLES_MAX_COUNT self._max_samples = max_samples self._max_shots = max_shots default_it = self._it_default_parameters() results = {'results_list': []} for idx, it in enumerate(self._iterator): self._apply_iteration(default_it | it) results['results_list'].append(self._processor.samples(self._max_samples, self._max_shots)) results['results_list'][-1]['iteration'] = it if progress_callback is not None: progress_callback((idx + 1) / len(self._iterator)) self._apply_iteration(default_it) # restore default parameters return results def _apply_iteration(self, it): for key, val in it.items(): try: self.__getattribute__(f"_set_{key}")(val) except AttributeError: pass def _set_circuit_params(self, params: dict): if params: circuit_params = self._processor.get_circuit_parameters() for name, value in params.items(): if value is not None: circuit_params[name].set_value(value) def _set_input_state(self, input_state: BasicState): self._processor.with_input(input_state) def _set_min_detected_photons(self, count: int): self._processor.min_detected_photons_filter(count) def _set_max_samples(self, val: int): self._max_samples = val def _set_max_shots(self, val: int): self._max_shots = val def _set_noise(self, noise: NoiseModel): self._processor.noise = noise def _it_default_parameters(self) -> dict: """Creates an iteration with default parameters""" input_state = self._processor.input_state if isinstance(input_state, BasicState) and self._processor.heralds: # If it's not a BasicState, the user needs to provide all the modes input_state = BasicState([v for m, v in enumerate(input_state) if m not in self._processor.heralds]) return {"circuit_params": {k: v._value for k, v in self._processor.get_circuit_parameters().items()}, "input_state": input_state, "min_detected_photons": self._processor.experiment.min_photons_filter, "max_samples": self._max_samples, "max_shots": self._max_shots, "noise": self._processor.noise } ================================================ FILE: perceval/algorithm/tomography/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .tomography_utils import is_physical, process_fidelity from .tomography import ProcessTomography, StateTomography from .tomography_mle import ProcessTomographyMLE, StateTomographyMLE from .abstract_process_tomography import AProcessTomography ================================================ FILE: perceval/algorithm/tomography/abstract_process_tomography.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import abstractmethod import numpy as np from perceval.algorithm.abstract_algorithm import AAlgorithm from perceval.components import AProcessor from .tomography_utils import _vector_to_sq_matrix, _krauss_repr_ops, _get_canonical_basis_ops class AProcessTomography(AAlgorithm): def __init__(self, processor: AProcessor, **kwargs): super().__init__(processor=processor, **kwargs) self._nqubit = processor.m // 2 if self._nqubit > 3: raise ValueError( f"Input gate too large. Tomography supports up to 3-qubit gates ({self._nqubit}-qubit gate passed).") self._size_hilbert = 2 ** self._nqubit @abstractmethod def chi_matrix(self) -> np.ndarray: pass def _beta_tensor_elem(self, j: int, k: int, m: int, n: int, nqubit: int) -> np.ndarray: """ computes the elements of beta^{mn}_{jk}, a rank 4 tensor, each index of which can take values between 0 and d^2-1 [d = _size_hilbert] :param j: one of the indices for the beta tensor, value between 0 and d**2-1 :param k: one of the indices for the beta tensor, value between 0 and d**2-1 :param m: one of the indices for the beta tensor, value between 0 and d**2-1 :param n: one of the indices for the beta tensor, value between 0 and d**2-1 :param nqubit: numbero f qubits :return: """ b = _krauss_repr_ops(m, _get_canonical_basis_ops(j, self._size_hilbert), n, nqubit) q, r = divmod(k, self._size_hilbert) # quotient, remainder return b[q, r] def _beta_as_matrix(self) -> np.ndarray: """ compiles the 2D beta matrix by extracting elements of the rank 4 tensor computed by method _beta_tensor_elem :return: Beta Matrix for Chi computation """ num_meas = self._size_hilbert ** 4 # Total number of measurements needed for process tomography beta_matrix = np.zeros((num_meas, num_meas), dtype=np.cdouble) for a in range(num_meas): j, k = divmod(a, self._size_hilbert ** 2) # returns quotient, remainder for b in range(num_meas): # j,k,m,n are indices for _beta_tensor_elem m, n = divmod(b, self._size_hilbert ** 2) beta_matrix[a, b] = self._beta_tensor_elem(j, k, m, n, self._nqubit) return beta_matrix def _lambda_target(self, operator: np.ndarray) -> np.ndarray: """ Implements a mathematical formula for ideal gate (given operator) to compute process fidelity :param operator: Target operator matrix :return: lambda vector to compute chi for the target operator """ lambda_matrix = np.zeros((self._size_hilbert ** 2, self._size_hilbert ** 2), dtype=np.cdouble) for j in range(self._size_hilbert ** 2): rhoj = _get_canonical_basis_ops(j, self._size_hilbert) eps_rhoj = np.linalg.multi_dot([operator, rhoj, np.conjugate(np.transpose(operator))]) for k in range(self._size_hilbert ** 2): quotient, remainder = divmod(k, self._size_hilbert) lambda_matrix[j, k] = eps_rhoj[quotient, remainder] L1 = np.zeros((self._size_hilbert ** 4, 1), dtype=np.cdouble) for i in range(self._size_hilbert ** 4): quotient, remainder = divmod(i, self._size_hilbert ** 2) L1[i] = lambda_matrix[quotient, remainder] return L1 def chi_target(self, operator: np.ndarray) -> np.ndarray: """ Implements a mathematical formula for ideal gate (given operator) to compute process fidelity :param operator: Target operator matrix :return: Target Chi matrix """ beta_inv = np.linalg.pinv(self._beta_as_matrix()) lambd = self._lambda_target(operator) X = np.dot(beta_inv, lambd) # X is a matrix here chi = _vector_to_sq_matrix(X[:, 0]) return chi ================================================ FILE: perceval/algorithm/tomography/tomography.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np from collections import defaultdict from perceval.components import AProcessor, PauliType from perceval.utils import BasicState from .abstract_process_tomography import AProcessTomography from .tomography_utils import (_matrix_basis, _matrix_to_vector, _vector_to_sq_matrix, _coef_linear_decomp, _get_fixed_basis_ops, _get_canonical_basis_ops, _generate_pauli_index, _generate_pauli_prep_index, _list_subset_k_from_n, _compute_probs) from ..abstract_algorithm import AAlgorithm class StateTomography(AAlgorithm): """ Experiment to reconstruct the state of the system by tomography experiment. - Adds preparation and measurement circuits to input processor (with the gate operation under study) - Computes parameters required to do state tomography - Performs Tomography experiment - Computes and Returns density matrices for each input state :param operator_processor: A perceval Processor with gate (or operation) on which state tomography needs to be performed. """ def __init__(self, operator_processor: AProcessor, **kwargs): super().__init__(processor=operator_processor, **kwargs) self._nqubit, odd_modes = divmod(operator_processor.m, 2) if odd_modes: raise ValueError( f"Input processor has an odd mode count ({operator_processor.m}) and thus, is not a logical gate") self._size_hilbert = 2 ** self._nqubit self._gate_logical_perf = None self._qst_cache = defaultdict(lambda: defaultdict(lambda: dict)) _LOGICAL0 = BasicState([1, 0]) _LOGICAL1 = BasicState([0, 1]) def _stokes_parameter(self, prep_state_indices: list, meas_pauli_basis_indices: list) -> float: """ Computes the Stokes parameter S_i for state prep_state_indices after operator_circuit :param prep_state_indices: list of length of number of qubits representing the preparation circuit :param meas_pauli_basis_indices: list of length of number of qubits representing the measurement circuit and the eigenvectors being measured :return: Value of Stokes parameter for a given combination of input and output state -> a complex float """ if PauliType.Z not in meas_pauli_basis_indices: output_distribution, self._gate_logical_perf = _compute_probs(self, prep_state_indices, meas_pauli_basis_indices) self._qst_cache[tuple(prep_state_indices)][tuple(meas_pauli_basis_indices)] = output_distribution else: meas_indices_Z_to_I = [elem if elem != PauliType.Z else PauliType.I for elem in meas_pauli_basis_indices] output_distribution = self._qst_cache[tuple(prep_state_indices)][tuple(meas_indices_Z_to_I)] # calculation of the Stokes parameter begins here stokes_param = 0 for k in range(self._nqubit + 1): for J in _list_subset_k_from_n(k, self._nqubit): eta = 1 measurement_state = BasicState() for j in range(0, self._nqubit): if j not in J: measurement_state *= self._LOGICAL0 else: measurement_state *= self._LOGICAL1 if meas_pauli_basis_indices[j] != PauliType.I: eta *= -1 stokes_param += eta * output_distribution[measurement_state] return stokes_param def perform_state_tomography(self, prep_state_indices: list) -> np.ndarray: """ Computes the density matrix of a state after the operator_circuit. Size d x d where d=size_of_hilbert_space :param prep_state_indices: list of length of number of qubits to index the corresponding preparation circuit :return: density matrix for a given input state preparation. size_hilbert x size_hilbert array. """ density_matrix = np.zeros((self._size_hilbert, self._size_hilbert), dtype=np.cdouble) pauli_meas_indices = _generate_pauli_index(self._nqubit) # generates indices for measurement for index, elem in enumerate(pauli_meas_indices): density_matrix += self._stokes_parameter(prep_state_indices, elem) \ * _get_fixed_basis_ops(index, self._nqubit) density_matrix = ((1 / 2) ** self._nqubit) * density_matrix return density_matrix class ProcessTomography(AProcessTomography): """ Experiment to reconstruct the process map of the gate operation by tomography experiment. - Computes the mathematical tensors/matrices defined by theory required to perform process tomography. - Computes Chi matrix form of the operation process map. - Provides analysis methods to investigate the results of process tomography such as the fidelity of the operation and error process maps. :param operator_processor: A perceval Processor with gate (or operation) on which process tomography needs to be performed """ def __init__(self, operator_processor: AProcessor, **kwargs): super().__init__(processor=operator_processor, **kwargs) self._qst = StateTomography(operator_processor=self._processor, **kwargs) self.chi_normalized = None self.chi_unnormalized = None self.gate_efficiency = None self._prep_basis_size = 4 # Standard Process tomography works with a subset of pauli eigenstates prepared at input : |0>, |1>, |+>, |i+> def _lambda_vector(self) -> np.ndarray: """ Computes the lambda vector of the operator :return: Lambda vector for Chi computation """ density_matrices = [] # stores a list of density matrices for each measurement pauli_prep_indices = _generate_pauli_prep_index(self._nqubit, self._prep_basis_size) for prep_state_indices in pauli_prep_indices: # compute state of system for each preparation state - perform state tomography density_matrices.append(self._qst.perform_state_tomography(prep_state_indices)) # this creates the fixed basis for the Pauli states prepared 0, 1, + and i lambda_matrix = np.zeros((self._size_hilbert ** 2, self._size_hilbert ** 2), dtype=np.cdouble) for j in range(self._size_hilbert ** 2): rhoj = _get_canonical_basis_ops(j, self._size_hilbert) mu = _coef_linear_decomp(rhoj, _matrix_basis(self._nqubit, self._size_hilbert)) eps_rhoj = sum([mu[i] * density_matrices[i] for i in range(self._size_hilbert ** 2)]) for k in range(self._size_hilbert ** 2): quotient, remainder = divmod(k, self._size_hilbert) lambda_matrix[j, k] = eps_rhoj[quotient, remainder] return _matrix_to_vector(lambda_matrix) def chi_matrix(self) -> np.ndarray: """ Computes the chi matrix of the operator_circuit. :return: Chi matrix normalized by gate efficiency (=its trace). """ # Size of chi: d^4 x d^4 [=2**(2*nqubit)x2**(2*nqubit) array] if self.chi_normalized is None: beta_inv = np.linalg.pinv(self._beta_as_matrix()) L = self._lambda_vector() X = np.dot(beta_inv, L) # X is a vector here self.chi_unnormalized = _vector_to_sq_matrix(X) self.gate_efficiency = np.trace(self.chi_unnormalized) self.chi_normalized = self.chi_unnormalized / self.gate_efficiency return self.chi_normalized # always returns normalized chi map def average_fidelity(self, operator: np.ndarray) -> float: """ Computes the average fidelity of an operator (ideal) and its implementation (realistic). This is not a full fidelity of the operation as given by the process_fidelity but simply that of the gate. :param operator: operator (gate) matrix whose fidelity is to be calculated :return: the computed fidelity - between 0 and 1 """ Udag = np.transpose(np.conjugate(operator)) avg_fidelity = 1 / (self._size_hilbert + 1) # compute the map on a basis of states (tensor products of |0>, |1>, |+>,|i+>) density_matrices = [] # stores a list of density matrices for each measurement pauli_prep_indices = _generate_pauli_prep_index(self._nqubit, self._prep_basis_size) for prep_state_indices in pauli_prep_indices: density_matrices.append(self._qst.perform_state_tomography(prep_state_indices)) # setting values density_matrices = [x / self._qst._gate_logical_perf for x in density_matrices] for j in range(self._size_hilbert ** 2): Uj = _get_fixed_basis_ops(j, self._nqubit) mu = _coef_linear_decomp(Uj, _matrix_basis(self._nqubit, self._size_hilbert)) eps_Uj = sum([mu[i] * density_matrices[i] for i in range(self._size_hilbert ** 2)]) # compute the map on a basis Ujdag = np.transpose(np.conjugate(Uj)) a = np.linalg.multi_dot([operator, Ujdag, Udag, eps_Uj]) avg_fidelity += (1 / ((self._size_hilbert + 1) * (self._size_hilbert ** 2))) * np.trace(a) return np.real(avg_fidelity) def error_process_matrix(self, computed_chi: np.ndarray, operator: np.ndarray) -> np.ndarray: """ Computes the error matrix for an operation from the computed chi matrix. :param computed_chi: chi matrix computed from process tomography. :param operator: Gate (or operator) matrix. :return: error process matrix. """ # Size of Error process map: d^4 x d^4 V = np.zeros((self._size_hilbert ** 2, self._size_hilbert ** 2), dtype=np.cdouble) for m in range(self._size_hilbert ** 2): for n in range(self._size_hilbert ** 2): Emdag = np.transpose(np.conjugate(_get_fixed_basis_ops(m, self._nqubit))) En = _get_fixed_basis_ops(n, self._nqubit) Udag = np.transpose(np.conjugate(operator)) V[m, n] = (1 / self._size_hilbert) * np.trace(np.linalg.multi_dot([Emdag, En, Udag])) return np.linalg.multi_dot([V, computed_chi, np.conjugate(np.transpose(V))]) ================================================ FILE: perceval/algorithm/tomography/tomography_mle.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import abstractmethod import copy import numpy as np from scipy.linalg import sqrtm from .abstract_process_tomography import AProcessTomography from .tomography_utils import _state_to_dens_matrix, _matrix_to_vector, _get_fixed_basis_ops, _compute_probs, \ _generate_pauli_prep_index, _generate_pauli_index, _index_num_to_basis from perceval.utils import BasicState from perceval.utils.algorithms.norm import frobenius_inner_product from perceval.components import (AProcessor, PauliType, PauliEigenStateType, get_pauli_eigenvector_matrix, get_pauli_eigenvectors) from ..abstract_algorithm import AAlgorithm class TomographyMLE(AAlgorithm): """ Maximum likelihood Estimation for Quantum Tomography with an Accelerated Projected Gradient descent algorithm which takes an input guess and uses measurements to reconstruct quantum maps - either state or process. """ def __init__(self, operator_processor: AProcessor, **kwargs): super().__init__(processor=operator_processor, **kwargs) self._nqubit, odd_modes = divmod(operator_processor.m, 2) if odd_modes: raise ValueError( f"Input processor has an odd mode count ({operator_processor.m}) and thus, is not a logical gate") self._gate_logical_perf = None # following parameters are for the reconstruction algorithm self.decelerate_factor = kwargs.get('decelerate_factor', 0.5) # param decelerate_factor: Decreases the learning rate for a slower descent with a value in range (0,1) self.init_learn_rate = kwargs.get('init_learn_rate', 1) # param init_learn_rate: initial learning rate self.max_iterations = kwargs.get('max_iterations', 100) # :param max_iterations: maximum number of iterations self.convergence_precision = kwargs.get('convergence_precision', 1e-10) # precision for convergence of the algorithm _LOGICAL0 = BasicState([1, 0]) _LOGICAL1 = BasicState([0, 1]) @abstractmethod def _log_likelihood_func(self, *args, **kwargs) -> float: pass @abstractmethod def _grad_log_likelihood_func(self, *args, **kwargs) -> np.ndarray: pass @abstractmethod def _povm_data(self): pass def _collect_data(self, prep_state_indices, state_meas_indices) -> list: # performs measurements on the output_state for given preparation and measurement state indices at each qubit output_distribution, self._gate_logical_perf = _compute_probs(self, prep_state_indices, state_meas_indices, denormalize=False) measured_output = [] for j in range(2**self._nqubit): measurement_state = BasicState() for m in range(self._nqubit - 1, -1, -1): if (j // (2 ** m)) % 2 == 0: measurement_state *= self._LOGICAL0 else: measurement_state *= self._LOGICAL1 measured_output.append(output_distribution[measurement_state] / (3 ** self._nqubit)) return measured_output def _povm_state(self) -> list: # Gives a set of measurement states, so they form a set of informationally complete measurements. # For 1 qubit, they are (order important) : |0>,|1>,|+>,|->,|i+>,|i->, # These measurement states are eigenvectors of the tensor products of Pauli operators d = 2 ** self._nqubit povm_state = [] basis = list(PauliEigenStateType) pauli_eigen_basis = [] for pauli_eigenv in basis: pauli_eigen_basis.append(get_pauli_eigenvectors(pauli_eigenv)) for pauli_index in range(3 ** self._nqubit): # iterates over pauli operators I(equivalent to Z),X,Y or their tensor products # pauli_list = [] # saves on each element the pauli index for this qubit # pauli_list = _index_num_to_basis(pauli_index, self._nqubit, 3) X = [np.array([1], dtype=np.cdouble)] * d # neutral elements for tensor products for qubit_index in range(self._nqubit): for eigenvector_index in range(d): # iterates over the eigenvectors of the pauli operator eigenvector_list = _index_num_to_basis(eigenvector_index, self._nqubit, 2) X[eigenvector_index] = np.kron(X[eigenvector_index], pauli_eigen_basis[2 * pauli_list[qubit_index] + eigenvector_list[qubit_index]]) # X[] = Kroneckor_product (X[], pauli_eigen_basis[] + eigenvector[]) povm_state += X return povm_state def _povm_operator(self) -> list: # Gives a POVM (positive operator value measure) set suited for tomography B = self._povm_state() povm_op = [] for state in B: povm_op.append(_state_to_dens_matrix(state) / (3 ** self._nqubit)) return povm_op def _input_basis(self) -> list: # Computes input density matrix basis (similar to POVM but not same order) input_basis = [] for j in range(6 ** self._nqubit): k = _index_num_to_basis(j, self._nqubit, 6) M = 1 v = 1 for i in reversed(k): M = np.kron(get_pauli_eigenvector_matrix(PauliType(i // 2)), M) # Kronecker_product(pauli_eigen_vector[index],M) if i % 2 == 0: v = np.kron([[1], [0]], v) else: v = np.kron([[0], [1]], v) input_basis.append(_state_to_dens_matrix(np.dot(M, v))) return input_basis @staticmethod def _proj_simplex(eigenvalues: list) -> list: # Projects a given real eigen-spectra (sorted in descending order) on positive elements with their sum equal to 1 u = 0 for j in range(1, len(eigenvalues) + 1): x = eigenvalues[j - 1] - (1 / j) * (sum(eigenvalues[:j]) - 1) if x > 0: u = j if u == 0: w = 0 else: w = (1 / u) * (sum(eigenvalues[:u]) - 1) return [max(lambda_i - w, 0) for lambda_i in eigenvalues] @staticmethod def _proj(h_matrix: np.ndarray) -> np.ndarray: # Projects a given hermitian matrix on the cone of positive semi-definite trace=1 matrices eigenvalues, eigenvectors = np.linalg.eigh(h_matrix) L = TomographyMLE._proj_simplex(np.flip(eigenvalues)) L.reverse() x_0 = _state_to_dens_matrix(np.transpose([eigenvectors[:, 0]])) x = (L[0] / np.trace(x_0)) * x_0 for i in range(1, len(eigenvalues)): x_i = _state_to_dens_matrix(np.transpose([eigenvectors[:, i]])) x += (L[i] / np.trace(x_i)) * x_i return x def _perform_mle_tomography(self, init_guess_quantum_map: np.ndarray) -> np.ndarray: # Accelerated Projected Gradient descent algorithm which takes an input guess and # uses measurements to reconstruct quantum maps (state or process) using MLE for Quantum Tomography # ref: https://doi.org/10.48550/arXiv.1609.07881 # # param init_guess_quantum_map: an initial guess for the quantum map guess_quantum_map = copy.deepcopy(init_guess_quantum_map) init_quantum_map = copy.deepcopy(init_guess_quantum_map) theta = 1 ith_learn_rate = self.init_learn_rate # initializing the learning rate of the algorithm log_f_guess_quantum_map = self._log_likelihood_func(guess_quantum_map) grad_log_f_guess_quantum_map = self._grad_log_likelihood_func(guess_quantum_map) for _ in range(self.max_iterations): ith_quantum_map = self._proj(guess_quantum_map - ith_learn_rate * grad_log_f_guess_quantum_map) delta_i = ith_quantum_map - guess_quantum_map # difference between current and target log_f_ith_quantum_map = self._log_likelihood_func(ith_quantum_map) frob_prod_grad_log_f_delta = frobenius_inner_product(grad_log_f_guess_quantum_map, delta_i) norm_delta_i = (1 / (2 * ith_learn_rate)) * np.linalg.norm(delta_i, ord='fro') ** 2 while log_f_ith_quantum_map > (log_f_guess_quantum_map + frob_prod_grad_log_f_delta + norm_delta_i): ith_learn_rate *= self.decelerate_factor ith_quantum_map = TomographyMLE._proj(guess_quantum_map - ith_learn_rate * grad_log_f_guess_quantum_map) delta_i = ith_quantum_map - guess_quantum_map delta_i_hat = ith_quantum_map - init_quantum_map if frobenius_inner_product(delta_i, delta_i_hat) < 0: # Restart by re-initializing guess maps ith_quantum_map, guess_quantum_map, theta = init_quantum_map, init_quantum_map, 1 else: # Accelerate the algorithm in the next iteration theta, guess_quantum_map = (1 + np.sqrt(1 + 4 * theta ** 2)) / 2, ith_quantum_map + delta_i_hat * (theta - 1) / ( (1 + np.sqrt(1 + 4 * theta ** 2)) / 2) if np.abs(log_f_ith_quantum_map - self._log_likelihood_func(init_quantum_map)) < self.convergence_precision: break init_quantum_map = ith_quantum_map return ith_quantum_map class StateTomographyMLE(TomographyMLE): """ Maximum likelihood estimations to reconstruct quantum state density matrices. :param operator_processor: A perceval processor with gate (or operation) on which state tomography needs to be performed. """ def __init__(self, operator_processor, **kwargs): super().__init__(operator_processor, **kwargs) self._guess_density_matrix = np.eye(2 ** self._nqubit) / (2 ** self._nqubit) self._povm_data() # to set self._data_function -> all the measured data def _povm_data(self): # Performing a POVM (positive operator value measure) on the quantum processor # in the informationally complete Pauli basis, i.e. choosing all the eigenvectors of the Pauli operators. # They are |0>,|1>,|+>,|->,|i+>,|i-> measurement_indices = _generate_pauli_index(self._nqubit) preparation_indices = [PauliEigenStateType.Zm] * self._nqubit # Input Preparation fixed to |0> for state tomography data_function = [] for val in measurement_indices: if PauliType.Z in val: continue data_function += self._collect_data(preparation_indices, state_meas_indices=val) self._data_function = data_function def _log_likelihood_func(self, rho: np.ndarray) -> float: # Log-likelihood function of the POVM to minimize povm_op = self._povm_operator() x = 0 for k in range(len(self._data_function)): if np.trace(np.dot(rho, povm_op[k])) != 0: x -= self._data_function[k] * np.log(np.trace(np.dot(rho, povm_op[k]))) # data_function[index] * Log(Tr(rho.operator)) return x def _grad_log_likelihood_func(self, rho: np.ndarray) -> float: # Gradient of the log-likelihood function of the POVM povm_op = self._povm_operator() grad = 0 for k in range(len(self._data_function)): if np.trace(np.dot(rho, povm_op[k])) != 0: grad -= (self._data_function[k] / (np.trace(np.dot(rho, povm_op[k])))) * povm_op[k] # (data_function[index] / Tr(rho . povm_operator) ) *povm_operator return grad def state_tomography_density_matrix(self) -> np.ndarray: return self._perform_mle_tomography(self._guess_density_matrix) @staticmethod def state_fidelity(target_state_dm: np.ndarray, computed_state_dm: np.ndarray) -> float: """ Computes the fidelity of the density matrix reconstructed after State Tomography using MLE algorithm :param target_state_dm: target Density Matrix of the State :param computed_state_dm: reconstructed Density Matrix of the State :return: fidelity of the reconstructed state """ rx = sqrtm(target_state_dm) z = np.linalg.multi_dot([rx, computed_state_dm, rx]) return np.real(np.trace(sqrtm(z)) ** 2) class ProcessTomographyMLE(TomographyMLE, AProcessTomography): """ Maximum likelihood estimations to reconstruct a given quantum process. :param operator_processor: A perceval processor with gate (or operation) on which state tomography needs to be performed. """ def __init__(self, operator_processor, **kwargs): TomographyMLE.__init__(self, operator_processor=operator_processor, **kwargs) AProcessTomography.__init__(self, processor=operator_processor, **kwargs) self._povm_data() self._guess_choi_seed = np.eye((2 ** self._nqubit), dtype=np.cdouble) self._guess_choi_matrix = np.kron(self._guess_choi_seed, self._guess_choi_seed) / 16 def _povm_data(self): # Performing a POVM (positive operator value measure) on the quantum processor # in the informationally complete Pauli basis, i.e. choosing all the eigenvectors of the Pauli operators. # They are |0>,|1>,|+>,|->,|i+>,|i-> # measurement is always on 3 Paulitype I, X, Y : Z is moved away # prep has 6 options -> 4 pauli and 2 other are some combo # of that itself -> find which and decide how to implement preparation_states = _generate_pauli_prep_index(self._nqubit) measurement_states = _generate_pauli_index(self._nqubit) data_function = [] for value in preparation_states: f_per_prep = [] for meas_indices in measurement_states: if PauliType.Z in meas_indices: continue f_per_prep += self._collect_data(prep_state_indices=value, state_meas_indices=meas_indices) data_function.append(f_per_prep) self._data_function = data_function def _log_likelihood_func(self, choi_matrix: np.ndarray) -> float: # Log-likelihood function of the POVM to minimize povm_op = self._povm_operator() input_basis = self._input_basis() x = 0 for m in range(len(input_basis)): for l in range(len(povm_op)): pml = 2 ** self._nqubit * np.real(np.trace(np.dot(choi_matrix, np.kron(np.transpose(input_basis[m]), povm_op[l])))) # 2^n * Real(Tr(choi_matrix . Kronecker_product(basis[index1].T, povm_operator[index]))) if 0 < pml <= 1: x -= self._data_function[m][l] * np.log(pml) return x def _grad_log_likelihood_func(self, choi_matrix: np.ndarray) -> np.ndarray: # Gradient of the log-likelihood function of the POVM povm_op = self._povm_operator() input_basis = self._input_basis() grad = 0 # a numpy array, not a number for l in range(len(povm_op)): for m in range(len(input_basis)): pml = 2 ** self._nqubit * np.real(np.trace(np.dot(choi_matrix, np.kron(np.transpose(input_basis[m]), povm_op[l])))) # 2^n * Real(Tr(choi_matrix . Kronecker_product(basis[index1].T, povm_operator[index]))) if 0 < pml <= 1: grad -= (self._data_function[m][l] / pml) * np.kron(np.transpose(input_basis[m]), povm_op[l]) # (data_func / pml) * Kronecker_product(basis[index1].T, povm_operator[index]))) return grad def chi_matrix(self) -> np.ndarray: """ Computes the chi matrix of the quantum process under study using the MLE tomography :return: chi matrix """ choi = self._perform_mle_tomography(self._guess_choi_matrix) X = np.zeros((len(choi), len(choi)), dtype=np.cdouble) for m in range(len(choi)): P_m = np.conjugate(np.transpose(_matrix_to_vector(np.transpose(_get_fixed_basis_ops(m, self._nqubit))))) for n in range(len(choi)): X[m, n] = (1 / 2 ** self._nqubit) * np.linalg.multi_dot( [P_m, choi, _matrix_to_vector(np.transpose(_get_fixed_basis_ops(n, self._nqubit)))]) return X ================================================ FILE: perceval/algorithm/tomography/tomography_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np import math from itertools import product, combinations from perceval.components import (PauliType, PauliEigenStateType, get_pauli_gate, get_pauli_eigen_state_prep_circ, processor_circuit_configurator) from ..sampler import Sampler from perceval.utils import BasicState def _compute_probs(tomography_experiment, prep_state_indices: list, meas_pauli_basis_indices: list, denormalize = True) -> tuple: """ computes the output probability distribution for the tomography experiment :param tomography_experiment: Tomography experiment object with a Processor on which Tomography is to be done :param prep_state_indices: List of "nqubit" indices selecting the circuit at each qubit for a preparation state :param meas_pauli_basis_indices: List of "nqubit" indices selecting the circuit at each qubit for a measurement circuit :return: Output state probability distribution """ p = processor_circuit_configurator(tomography_experiment._processor, prep_state_indices, meas_pauli_basis_indices) input_state = BasicState([1, 0] * tomography_experiment._nqubit) p.with_input(input_state) p.compute_physical_logical_perf(True) sampler = Sampler(p, max_shots_per_call=tomography_experiment._max_shots) sampler.default_job_name = 'Tomography_'\ +str().join([x.name for x in prep_state_indices])+'_'\ +str().join([x.name for x in meas_pauli_basis_indices]) probs = sampler.probs() output_distribution = probs["results"] if sum(list(output_distribution.values())) == 0: raise ValueError("Null probabilities detected. Increase the number of max_shots_per_call for " "better samples in Tomography experiments") gate_logical_perf = probs["logical_perf"] if denormalize: for key in output_distribution: # Denormalize output state distribution output_distribution[key] *= gate_logical_perf return output_distribution, gate_logical_perf def _state_to_dens_matrix(state: np.ndarray) -> np.ndarray: r""" computes the density matrix representation of a state :math:'$\rho = |\psi> <\psi|$' given a state :math:'$|\psi>$' :param state: state of the system :return: density matrix """ return np.dot(state, np.conjugate(np.transpose(state))) def _matrix_basis(nqubit: int, d: int) -> list: """ Generate a list of basis matrices by choosing all combinations of the tensor products of basis states :param nqubit: number of qubits :param d: Size of hilbert space :return: list of basis matrices """ B = [] pauli_indices = _generate_pauli_prep_index(nqubit, prep_basis_size=4) v = np.zeros((d, 1), dtype=np.cdouble) v[0] = 1 for elem in pauli_indices: M = get_pauli_eigen_state_prep_circ(elem[0]).compute_unitary() if len(elem) > 1: for i in elem[1:]: M = np.kron(M, get_pauli_eigen_state_prep_circ(i).compute_unitary()) B.append(_state_to_dens_matrix(np.dot(M, v))) return B def _matrix_to_vector(matrix: np.ndarray) -> np.ndarray: """ Reforms a given matrix into a vector by concatenating row wise from the matrix :param matrix: any matrix of shape d*d :return: a vector of length d**2 """ return np.ndarray.flatten(matrix) def _vector_to_sq_matrix(vector: np.ndarray) -> np.ndarray: """ Reforms a vector of size d**2 into a matrix of size d*d :param vector: vector of d**2 length :return: Matrix of shape d$d """ size = math.sqrt(len(vector)) if not size.is_integer(): raise ValueError("Vector length incompatible to turn into a square matrix") # checks integer repr of float return np.reshape(vector, (int(size), int(size))) def _coef_linear_decomp(matrix: np.ndarray, basis: list) -> np.ndarray: """ Solves the linear system of equations : Matrix (M) = Mu x Basis (B) to find Mu. "Matrix" can be linearly decomposed in any given "Basis" :param matrix: a matrix 'M' to decompose :param basis: set of basis matrices 'B' in which the matrix 'M' is to be decomposed :return: an array of coefficients 'Mu' which satisfies the equation M = Mu X B """ basis_vectors = np.column_stack([_matrix_to_vector(m) for m in basis]) # convert to list of basis vectors y = _matrix_to_vector(matrix) # Solve the equation Matrix = Mu x Basis mu = np.linalg.solve(basis_vectors, y) return mu def _get_fixed_basis_ops(j: int, nqubit: int) -> np.ndarray: """ computes the set of operators (tensor products of pauli gates) in the fixed basis as an array of shape (size_hilbert x size_hilbert) :param j: j - number of measurements for state tomography = int between [0,size_hilbert**2 - 1] :param nqubit: number of qubits :return: operators in a fixed basis (Pauli) """ if nqubit == 1: return get_pauli_gate(PauliType(j)) q, r = divmod(j, (4 ** (nqubit - 1))) fix_basis_op = get_pauli_gate(PauliType(q)) # I am not sure if i completely follow the logic behind what follows. I believe the idea is # to keep val between 0-3 no matter the nqubits and value of j. # it seems similar to many other calls of creating combinations of operators in a basis # but when I called similarly, values for computed fidelity was different for i in reversed(range(nqubit - 1)): qq, rr = divmod(r, 4**i) fix_basis_op = np.kron(fix_basis_op, get_pauli_gate(PauliType(qq))) r = r % (4 ** i) # updating r for next loop - does not do much for nqubit=2 but would affect values >2 return fix_basis_op def _get_canonical_basis_ops(j: int, d: int) -> np.ndarray: """ computes the set of operators defined in canonical (standard) basis. Array of size (d x d) :param j: number of measurements for state tomography = int between [0, d**2 - 1] :param d: size of Hilbert space = 2**nqubits :return: operators in a canonical basis """ quotient, remainder = divmod(j, d) canonical_op = [[1 if i == quotient and j == remainder else 0 for j in range(d)] for i in range(d)] return np.array(canonical_op, dtype=np.cdouble) def _krauss_repr_ops(m: int, rhoj: np.ndarray, n: int, nqubit: int) -> np.ndarray: """ computes the Krauss representation of an operator = (fixed_basis_op x (canonical_basis_op x fixed_basis_op)) x: dot product in the previous line :param m: indices running between 0 -> d**2 - 1 :param rhoj: operators in a canonical basis :param n: indices running between 0 -> d**2 - 1 :param nqubit: number of qubits :return: Operators in Krauss representation """ return np.dot(_get_fixed_basis_ops(m, nqubit), np.dot(rhoj, np.conjugate(np.transpose(_get_fixed_basis_ops(n, nqubit))))) def _generate_pauli_index(nqubit: int) -> list: """ generates all possible combinations of Pauli operator indices repeated n times :param nqubit: number of qubits :return: List of Pauli operator indices """ s = [pt for pt in PauliType] return [list(p) for p in product(s, repeat=nqubit)] # takes Cartesian product of s with itself repeating 'n' times def _generate_pauli_prep_index(nqubit: int, prep_basis_size: int = None) -> list: """ generates all possible combinations of indices for pauli eigenstates repeated n times :param nqubit: number of qubits :param prep_basis_size: allows choosing a subset of states from Pauli eigenstates :return: List of Pauli eigenstate indices """ if prep_basis_size is None: # uses the full set Zm, Zp, Xp, Xm, Yp, Ym s = [pt for pt in PauliEigenStateType] else: # standard tomography requires only 4 eigenstates - Zm, Zp, Xp, Yp s = [PauliEigenStateType.Zm, PauliEigenStateType.Zp, PauliEigenStateType.Xp, PauliEigenStateType.Yp] return [list(p) for p in product(s, repeat=nqubit)] def _list_subset_k_from_n(k: int, n: int) -> list: """ list of distinct combination sets of length k from set 's' where 's' is the set {0,...,n-1} used only in the method _stokes_parameter :param k: index to iterate over the number of qubits :param n: number of qubits :return: combinations of sets of size k """ s = tuple(range(n)) return list(combinations(s, k)) def is_physical(input_matrix: np.ndarray, nqubit: int, eigen_tolerance: float = 1e-6) -> dict: """ Verifies if a matrix is trace preserving, hermitian, and completely positive (using the Choi matrix) :param input_matrix: chi of a quantum map computed from Quantum Process Tomography :param nqubit: Number of qubits :param eigen_tolerance: brings a tolerance for the positivity of the eigenvalues of the Choi matrix :return: Results about the tests : if Trace Preserving (TP), Hermitian, Completely Positive (CP) """ d2 = len(input_matrix) res = {} # check if trace preserving if not np.isclose(np.trace(input_matrix), 1): res['Trace=1'] = False else: res['Trace=1'] = True # check if hermitian hermiticity = np.zeros_like(input_matrix).real for i in range(d2): for j in range(i, d2): if not np.isclose(input_matrix[i][j], np.conjugate(input_matrix[j][i])): hermiticity[i][j] = 1 if np.any(hermiticity == 1): res['Hermitian'] = False else: res['Hermitian'] = True # check if completely positive with Choi–Jamiołkowski isomorphism choi = 0 for n in range(d2): P_n = np.conjugate([_matrix_to_vector(np.transpose(_get_fixed_basis_ops(n, nqubit)))]) for m in range(d2): choi += input_matrix[m, n] \ * np.dot(np.transpose([_matrix_to_vector(np.transpose(_get_fixed_basis_ops(m, nqubit)))]), P_n) choi /= 2 ** nqubit eigenvalues = np.linalg.eigvalsh(choi) if np.any(eigenvalues < -eigen_tolerance): res['Completely Positive'] = False else: res['Completely Positive'] = True return res def _index_num_to_basis(index, nqubit, basis_size) -> list: digits = [] for j in range(nqubit - 1, -1, -1): digits.append(index // (basis_size ** j)) index = index % (basis_size ** j) return digits def process_fidelity(computed_map: np.ndarray, ideal_map: np.ndarray) -> float: """ Computes the process fidelity of an operator (ideal) and its implementation (realistic) :param computed_map: process map (chi matrix) or density matrix map computed by tomography :param ideal_map: ideal process map (chi matrix) or density matrix map of the process or state :return: float between 0 and 1 """ return np.real(np.trace(np.dot(computed_map, ideal_map))) ================================================ FILE: perceval/backends/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from ._abstract_backends import ABackend, ASamplingBackend, AStrongSimulationBackend, IFFBackend from ._clifford2017 import Clifford2017Backend from ._mps import MPSBackend from ._naive import NaiveBackend from ._naive_approx import NaiveApproxBackend from ._slos import SLOSBackend from ._slap import SLAPBackend BACKEND_LIST = { "CliffordClifford2017": Clifford2017Backend, "MPS": MPSBackend, "Naive": NaiveBackend, "NaiveApprox": NaiveApproxBackend, "SLAP": SLAPBackend, "SLOS": SLOSBackend } class BackendFactory: @staticmethod def get_backend(backend_name: str = "SLOS", **kwargs) -> ABackend: name = backend_name if name in BACKEND_LIST: return BACKEND_LIST[name](**kwargs) # Do not import from top level or you'll expose what's imported from perceval.utils.logging import get_logger, channel get_logger().warn(f'Backend "{name}" not found. Falling back on SLOS', channel.user) return BACKEND_LIST['SLOS'](**kwargs) @staticmethod def list(): return list(BACKEND_LIST.keys()) ================================================ FILE: perceval/backends/_abstract_backends.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import ABC, abstractmethod from typing import Iterable import exqalibur as xq from exqalibur import SVDistribution from perceval.components import ACircuit, AComponent, IDetector, Source from perceval.utils import BasicState, FockState, BSDistribution, BSSamples, StateVector, allstate_iterator, global_params class ABackend(ABC): def __init__(self): self._circuit = None self._umat = None self._input_state = None def set_circuit(self, circuit: ACircuit): """ Sets the circuit to simulate. This circuit must not contain polarized components (use PolarizationSimulator instead, if required). """ if circuit.requires_polarization: raise RuntimeError("Circuit must not contain polarized components") self._input_state = None self._circuit = circuit self._umat = circuit.compute_unitary() def set_input_state(self, input_state: FockState): """ Sets an input state for the simulation. This state has to be a Fock state without annotations. """ self._check_state(input_state) self._input_state = input_state def _check_state(self, state: FockState): assert self._circuit is not None, 'Circuit must be set before the input state' assert self._circuit.m == state.m, f'Circuit({self._circuit.m}) and state({state.m}) size mismatch' @property @abstractmethod def name(self) -> str: """Returns the back-end name as a string""" class ASamplingBackend(ABackend): @abstractmethod def sample(self) -> BasicState: """Request one sample from the circuit given an input state""" @abstractmethod def samples(self, count: int) -> BSSamples: """Request samples from the circuit given an input state""" class _StateProbIterator(Iterable[tuple[FockState, float]]): def __init__(self, states: Iterable[FockState], probs: Iterable[float]): self.states = states self.probs = probs def __iter__(self): return zip(self.states, self.probs) class AStrongSimulationBackend(ABackend): def __init__(self): super().__init__() self._mask_n = None self._cache_iterator: dict = dict() self._masks_str: list[str] | None = None self._mask: xq.FSMask | None = None self._no_limit_modes: list[int] | None = None def set_mask(self, masks: str | list[str], n = None, at_least_modes = None): r""" Sets new masks, replacing the former ones if they exist. Clear possible cached data that depend on the mask. Masks are useful to limit strong simulation to only a part of the Fock space, ultimately saving memory and computation time. :param masks: Can be a mask or a list of masks. Each mask is expressed as a string where each character is a condition on one mode. Digits are fixing the number of photons whereas spaces or "\*" are accepting any number of detections. e.g. using "\*\*\*\*00" as a mask limits the simulation to output states ending in two empty modes. :param n: The number of photons to instantiate the mask with. This corresponds to the total number of photons in your non-separated state. :param at_least_modes: A list containing the modes on which the accepted number of photons can be anything higher than or equal to the given value in the condition. """ self.clear_mask() if isinstance(masks, str): masks = [masks] mask_length = len(masks[0]) for m in masks: m = m.replace("*", " ") assert len(m) == mask_length, "Inconsistent mask lengths" self._masks_str = masks self._mask_n = n self._no_limit_modes = at_least_modes self._init_mask() def _init_mask(self): if self._masks_str is not None and self._input_state is not None: instate = self._input_state assert len(self._masks_str[0]) == instate.m, "Mask and input state lengths have to be the same" if self._no_limit_modes: self._mask = xq.FSMask(instate.m, self._mask_n or instate.n, self._masks_str, self._no_limit_modes) else: self._mask = xq.FSMask(instate.m, self._mask_n or instate.n, self._masks_str) def clear_mask(self): """ Removes any pre-existing mask """ self._masks_str = None self._mask = None self._mask_n = None self.clear_iterator_cache() def set_input_state(self, input_state: FockState): super().set_input_state(input_state) self._init_mask() def _get_iterator(self, input_state: BasicState): n_photons = input_state.n if n_photons not in self._cache_iterator.keys(): self._cache_iterator[n_photons] = tuple(allstate_iterator(input_state, self._mask)) return self._cache_iterator[n_photons] def clear_iterator_cache(self): self._cache_iterator = dict() def set_circuit(self, circuit: ACircuit): if self._circuit and circuit.m != self._circuit.m: self.clear_iterator_cache() super().set_circuit(circuit) @abstractmethod def prob_amplitude(self, output_state: FockState) -> complex: """Computes the probability amplitude for a given output state. The input state and the circuit must already be set""" pass def probability(self, output_state: FockState) -> float: """Computes the probability for a given output state. The input state and the circuit must already be set""" return abs(self.prob_amplitude(output_state)) ** 2 def all_prob(self, input_state: FockState = None) -> list[float]: """Computes the list of probabilities of all states (respecting the mask if any was set). The order of the states can be retrieved using `allstate_iterator()`""" if input_state is not None: self.set_input_state(input_state) results = [] for output_state in self._get_iterator(self._input_state): results.append(self.probability(output_state)) return results def prob_distribution(self) -> BSDistribution: """ Computes the probability distribution of all states (respecting the mask if any was set) under the form of a BSDistribution. """ bsd = BSDistribution() for output_state in self._get_iterator(self._input_state): bsd.add(output_state, self.probability(output_state)) return bsd def prob_iterator(self, min_p: float = global_params["min_p"]) -> Iterable[tuple[FockState, float]]: # DO NOT document for users probs = self.all_prob(self._input_state) states = [state for i, state in enumerate(self._get_iterator(self._input_state)) if probs[i] > min_p] return _StateProbIterator(states, [prob for prob in probs if prob > min_p]) def evolve(self) -> StateVector: """Evolves the input BasicState into a StateVector.""" res = StateVector() for output_state in self._get_iterator(self._input_state): res += output_state * self.prob_amplitude(output_state) return res class IFFBackend(ABC): @staticmethod @abstractmethod def can_simulate_feed_forward(components: list[tuple[tuple, AComponent]], input_state: BasicState | SVDistribution | tuple[Source, FockState], detectors: list[IDetector] = None) -> bool: """ :param components: The list of components in the circuit, containing at least one FFConfigurator. :param input_state: The input state to simulate. Can be a BasicState, a SVD, or a tuple[Source, FockState] :param detectors: The detectors to apply at the end of the computation. :return: True if the output of the simulation will be correct, False otherwise. """ pass @abstractmethod def set_feed_forward(self, components: list[tuple[tuple, AComponent]], m: int) -> None: """ Sets the components containing at least one FFConfigurator in the backend. This should be used in place of :code:`set_circuit` :param components: The list of components in the circuit, containing at least one FFConfigurator. :param m: The number of modes in the circuit. """ pass ================================================ FILE: perceval/backends/_clifford2017.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import exqalibur as xq from perceval.utils import FockState from perceval.components import ACircuit from ._abstract_backends import ASamplingBackend class Clifford2017Backend(ASamplingBackend): def __init__(self): super().__init__() self._clifford = xq.Clifford2017() def set_circuit(self, circuit: ACircuit): super().set_circuit(circuit) # Computes circuit unitary as _umat self._clifford.set_unitary(self._umat) def set_input_state(self, input_state: FockState): super().set_input_state(input_state) self._clifford.set_input_state(input_state) def sample(self): return self._clifford.sample() def samples(self, count: int): """ Request `count` samples from the circuit given an input state. Uses parallel processing, so it is much more efficient than calling `self.sample()` several times. """ return self._clifford.samples(count) @property def name(self) -> str: return "CliffordClifford2017" ================================================ FILE: perceval/backends/_mps.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import copy import numpy as np from math import factorial, comb from collections import defaultdict from ._abstract_backends import AStrongSimulationBackend from perceval.utils import FockState from perceval.components import ACircuit from perceval.components.unitary_components import PERM, Barrier from perceval.components._decompose_perms import decompose_perms class MPSBackend(AStrongSimulationBackend): """ The state of the system is written in form of an MPS and updated step-by-step by a circuit propagation algorithm. Approximate the probability amplitudes with a cutoff -> bond Dimension in an MPS. - For now only supports components for up to 2 modes (Phase shifters and Beam Splitters already implemented) :param cutoff: The bond dimension. Higher values mean better precision but slower computation. Default input_state.n + 1 """ def __init__(self, cutoff : int = None): super().__init__() self._s_min = 1e-8 # minimum accepted value for singular values self._cutoff = None # Bond dimension of MPS if cutoff is not None: self.set_cutoff(cutoff) self._compiled_input = None self._current_input = None self._res = defaultdict(lambda: defaultdict(lambda: np.array([0]))) # _res stores output of the state compilation in MPS. # It is a Nested DefaultDict. # 1st layer: Each "input_states" has the full MPS # 2nd layer: gamma or lambda keys and their numpy arrays @property def name(self) -> str: return "MPS" def set_cutoff(self, cutoff_val: int): """ Cut-off defines the Bond dimension (Schmidt rank of the decomposition of the state) of an MPS; in other words, how well approximated the state is. Default value is the total number of photons + 1. """ assert isinstance(cutoff_val, int), "cutoff must be an integer" self._cutoff = cutoff_val def set_circuit(self, circuit: ACircuit): super().set_circuit(circuit) C = self._circuit decomp_perm = False for r, c in C: assert isinstance(c, PERM) or isinstance(c, Barrier) or c.compute_unitary(use_symbolic=False).shape[0] <= 2, \ "MPS backend can not be used with components of using more than 2 modes other than a PERM" if isinstance(c, PERM) and c.m > 2: # sets the flag to True if n-mode PERM (n>2) is found in the circuit decomp_perm = True if decomp_perm: # decompose any n(>2) mode perm into 2-mode perms in the circuit self._circuit = decompose_perms(C) def set_input_state(self, input_state: FockState): super().set_input_state(input_state) self._compile() def prob_amplitude(self, output_state: FockState) -> complex: """ Computes the probability for a given input output state from a circuit with m modes and n photons computed using MPS """ # self._res extracts gamma and diagonal SV matrices corresponding to given output_state; this is # stored in list of matrices and multidot performs a full matrix multiplication overall of them # in a single statement. Optimized by numpy m = self._input_state.m mps_in_list = [] self._current_input = tuple(self._input_state) for k in range(m - 1): mps_in_list.append(self._res[tuple(self._input_state)]["gamma"][k, :, :, output_state[k]]) # _res[1ST LEVEL: selects given input state][2ND LEVEL: selects "gamma" matrix key of that] # [3RD LEVEL: for a gamma -> chooses kth mode and then segment # with #photons equal to that in output_state considered] mps_in_list.append(self._sv_diag(k)) mps_in_list.append(self._res[tuple(self._input_state)]["gamma"][m - 1, :, :, output_state[m - 1]]) # Inserting the last gamma into MPS outside the loop as there is no sv after that. return np.linalg.multi_dot(mps_in_list)[0, 0] def _compile(self) -> bool: var = [float(p) for p in self._circuit.get_parameters()] if self._compiled_input and self._compiled_input[0] == var and self._input_state in self._res: # checks if a given input state for a circuit is already computed return False self._compiled_input = copy.copy((var, self._input_state)) self._current_input = None self._n = self._input_state.n # total number of photons self._d = self._n + 1 # possible num of photons in each mode {0,1,2,...,n} if self._cutoff is None or self._cutoff < self._d: self._cutoff = self._d # sets the default value of cut-off to max number of photons (also min computation value needed) self._cutoff = min(self._cutoff, self._d ** (self._input_state.m//2)) # choosing a cut-off smaller than the limit as the size of matrix increases # exponentially with cutoff # this is the Schmidt's rank or bond dimension (chi in Thibaud's notes) self._gamma = np.zeros((self._input_state.m, self._cutoff, self._cutoff, self._d), dtype=np.cdouble) # Gamma matrices of the MPS - array shape (m, chi, chi, d) # Each Gamma matrix of MPS, in theory, have 3 indices. # The first index 'm' here is used to represent modes - all gammas of MPS stored in a single array for i in range(self._input_state.m): self._gamma[i, 0, 0, self._input_state[i]] = 1 self._sv = np.zeros((self._input_state.m, self._cutoff)) # sv matrices store singular values - array shape (m, chi) # sv are vectors of length chi. Similar to gamma, the first index 'm' indexes mode. self._sv[:, 0] = 1 # first column set to 1 # This initialization of MPS (gamma and sv) fixes the input state to be completely separable # and a pure BasicState (no superposition); hence would have only 1 non-zero element whose value = 1. # It is simply written based on this choice as the SVD of such a structure would exactly look like this # methods currently available in ITensors(Julia), Qiskit for r, c in self._circuit: # r -> tuple -> lists the modes where the component c is connected self._apply(r, c) self._res[tuple(self._input_state)]["gamma"] = self._gamma.copy() self._res[tuple(self._input_state)]["sv"] = self._sv.copy() return True def _apply(self, r, c): """ Applies the components of the circuit iteratively to update the MPS. :param r: List of the mode positions for a component of the Circuit :param c: The component """ u = c.compute_unitary(False) k_mode = r[0] # k-th mode is where the upper mode(only) of the BS(PS) component is connected if len(u) == 2: # BS self.update_state_2_mode(k_mode, u) elif len(u) == 1: # PS self.update_state_1_mode(k_mode, u) ######################################################################################## def update_state_1_mode(self, k, u): """ tensor contraction between the corresponding mode's gamma of the MPS and the transition matrix "U" of phase shifter for that mode [_transition_matrix_1_mode]. """ self._gamma[k] = np.tensordot(self._gamma[k], self._transition_matrix_1_mode(u), axes=(2, 0)) # gamma[k] -> takes the kth slice from first dimension -> selects gamma of kth mode # gamma[k].shape=(chi, chi, d) and _transition_matrix_1_mode(u).shape=(d, d). # The contraction is on the free index 'd'. # Assigns the result to the same gamma[k] returning the shape (chi, chi, d) def _transition_matrix_1_mode(self, u): """ transition matrix "U" related to the application of a phase shifter one a single mode. size of this "U" depends on the possible number of photons {0,1,2,...n} ==> d=n+1. returns the full transition matrix "U" related to the component that will update the corresponding mode's "gamma" of the matrix product state :param u: the unitary matrix for single mode component - PS :returns big_u: np.ndarray of the corresponding transition matrix """ d = self._d big_u = np.zeros((d, d), dtype=np.cdouble) for i in range(d): big_u[i, i] = u[0, 0] ** i return big_u def update_state_2_mode(self, k, u): """ takes the gamma->kth and (k+1)th + corresponding lambda-s -> contracts the entire thing with 2 mode beam splitter, performs some reshaping and then svd to re-build the corresponding segment of MPS. """ if 0 < k < self._input_state.m - 2: # BS anywhere except the first and the last mode # all gamma[k,:].shape=(chi, chi, d) and _sv_diag(k).shape=(chi, chi) theta = np.tensordot(self._sv_diag(k - 1), self._gamma[k, :], axes=(1, 0)) # theta.shape=(chi,chi,d) theta = np.tensordot(theta, self._sv_diag(k), axes=(1, 0)) # theta.shape=(chi, d, chi) theta = np.tensordot(theta, self._gamma[k + 1, :], axes=(2, 0)) # theta.shape=(chi, d, chi, d) theta = np.tensordot(theta, self._sv_diag(k + 1), axes=(2, 0)) # theta.shape=(chi, d, d, chi) # contraction of the corresponding matrices of MPS finished until here theta = np.tensordot(theta, self._transition_matrix_2_mode(u), axes=([1, 2], [0, 1])) # input->theta.shape=(chi, d, d, chi) and big_u.shape(d,d,d,d) # output->theta.shape(chi, chi, d, d) elif k == 0: # BS connected between the first 2 modes -> Edge of circuit # all gamma[k,:].shape=(chi, chi, d) and _sv_diag(k).shape=(chi, chi) theta = np.tensordot(self._gamma[k, :], self._sv_diag(k), axes=(1, 0)) # theta.shape=(chi, d, chi) theta = np.tensordot(theta, self._gamma[k + 1, :], axes=(2, 0)) # theta.shape=(chi, d, chi, d) theta = np.tensordot(theta, self._sv_diag(k + 1), axes=(2, 0)) # theta.shape=(chi, d, d, chi) theta = np.tensordot(theta, self._transition_matrix_2_mode(u), axes=([1, 2], [0, 1])) # input->theta.shape=(chi, d, d, chi) and big_u.shape(d,d,d,d) # output->theta.shape(chi, chi, d, d) elif k == self._input_state.m - 2: # BS connected between the last 2 modes -> Edge of circuit # all gamma[k,:].shape=(chi, chi, d) and _sv_diag(k).shape=(chi, chi) theta = np.tensordot(self._sv_diag(k - 1), self._gamma[k, :], axes=(1, 0)) # theta.shape=(chi,chi,d) theta = np.tensordot(theta, self._sv_diag(k), axes=(1, 0)) # theta.shape=(chi, d, chi) theta = np.tensordot(theta, self._gamma[k + 1, :], axes=(2, 0)) # theta.shape=(chi, d, chi, d) theta = np.tensordot(theta, self._transition_matrix_2_mode(u), axes=([1, 3], [0, 1])) # input->theta.shape = (chi, d, chi, d) and big_u.shape(d, d, d, d) # output->theta.shape(chi, chi, d, d) theta = theta.swapaxes(1, 2).swapaxes(0, 1).swapaxes(2, 3) # resulting theta.shape(d, chi, d, chi) theta = theta.reshape(self._d * self._cutoff, self._d * self._cutoff) # theta.shape (d x chi, d x chi) v, s, w = np.linalg.svd(theta) # svd of the tensor after component is applied to extract the MPS form # in standard notation SVD is written as M=USV, but we keep 'u' for unitary, # Here:: U->v [v.shape=(d x chi, d x chi)], (1st in the new MPS chain) # S->s [s.shape=(d x chi)], V->w [w.shape=(d x chi, d x chi)] v = v.reshape(self._d, self._cutoff, self._d * self._cutoff).swapaxes(0, 1).swapaxes(1, 2)[:, :self._cutoff] w = w.reshape(self._d * self._cutoff, self._d, self._cutoff).swapaxes(1, 2)[:self._cutoff] s = s[:self._cutoff] # restricting the size of SV matrices to cut_off -> truncation # updating corresponding sv after the action of BS self._sv[k] = np.where(s > self._s_min, s, 0) # updating self._gamma[k] :: uses v from SVD above if k > 0: rank = np.nonzero(self._sv[k - 1])[0][-1] + 1 self._gamma[k, :rank] = v[:rank] / self._sv[k - 1, :rank][:, np.newaxis, np.newaxis] self._gamma[k, rank:] = 0 else: self._gamma[k] = v # updating self._gamma[k+1] :: uses w from SVD above if k < self._input_state.m - 2: rank = np.nonzero(self._sv[k + 1])[0][-1] + 1 self._gamma[k + 1, :, :rank] = (w[:, :rank] / self._sv[k + 1, :rank][:, np.newaxis]) self._gamma[k + 1, :, rank:] = 0 else: self._gamma[k + 1] = w def _transition_matrix_2_mode(self, u): """ this function computes the elements (I,J) = (i_k, i_k+1, j_k, j_k+1) of the matrix U_k,k+1. This is concerned with the action of beam splitter between given 2 modes. input parameter u is the unitary matrix of the Beam splitter - 2x2 matrix The formula for constructing the larger U to contract with the MPS is in Thibaud report. """ u11, u12, u21, u22 = u[0, 0], u[0, 1], u[1, 0], u[1, 1] d = self._d # matrix corresponding to action of BS on the 2 modes big_u = np.zeros((d, d, d, d), dtype=np.cdouble) for n1 in range(d): # n1 -> number of photons in mode 1 for n2 in range(d): # n2 -> number of photons in mode 2 n_tot = n1 + n2 outputs = np.zeros((d, d), dtype=np.cdouble) # unitary of BS for a fixed n1 and n2 entering the modes if n_tot <= self._n: # cannot exceed the total number of photons in the circuit for k1 in range(n1+1): for k2 in range(n2+1): # of those (n1,n2) entering -> (k1,k2) combinations outputs[k1 + k2, n_tot - (k1 + k2)] += comb(n1, k1) * comb(n2, k2) \ * (u11**k1 * u12**(n1-k1) * u21**k2 * u22**(n2-k2)) \ * (np.sqrt(factorial(k1+k2) * factorial(n_tot-k1-k2))) big_u[n1, n2, :] = outputs / (np.sqrt(factorial(n1) * factorial(n2))) return big_u def _sv_diag(self, k): """ Creates the diagonal matrix containing the singular values of the matrices in the MPS. """ if self._res[self._current_input]["sv"].any(): sv = self._res[self._current_input]["sv"] else: sv = self._sv sv_diag = np.zeros((self._cutoff, self._cutoff)) np.fill_diagonal(sv_diag, sv[k, :]) return sv_diag ================================================ FILE: perceval/backends/_naive.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import numpy as np import exqalibur as xq from ._abstract_backends import AStrongSimulationBackend from perceval.utils import FockState class NaiveBackend(AStrongSimulationBackend): """Naive algorithm, no clever calculation path, does not cache anything, recompute all states on the fly""" @property def name(self) -> str: return "Naive" def prob_amplitude(self, output_state: FockState) -> complex: p = output_state.prodnfact() * self._input_state.prodnfact() M = self._compute_submatrix(output_state) return self._compute_permanent(M) / math.sqrt(p) if M.size > 1 else M[0, 0] def _compute_submatrix(self, output_state: FockState) -> np.ndarray: n = self._input_state.n m = self._input_state.m if n != output_state.n: return np.zeros((1, 1), dtype=complex) if n == 0: return np.ones((1, 1), dtype=complex) u_st = np.empty((n, n), dtype=complex) colidx = 0 for ik in range(m): for _ in range(self._input_state[ik]): rowidx = 0 for ok in range(m): for _ in range(output_state[ok]): u_st[rowidx, colidx] = self._umat[ok, ik] rowidx += 1 colidx += 1 return u_st def _compute_permanent(self, m): return xq.permanent_cx(m) ================================================ FILE: perceval/backends/_naive_approx.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import exqalibur as xq from ._naive import NaiveBackend from perceval.utils import FockState class NaiveApproxBackend(NaiveBackend): """ Naive algorithm with Gurvits computations of permanents :param gurvits_iterations: Number of iterations to use for Gurvits estimation algorithm (default 10 000) """ def __init__(self, gurvits_iterations: int = 10000): self._gurvits_iterations = gurvits_iterations NaiveBackend.__init__(self) @property def name(self) -> str: return "NaiveApprox" def _compute_permanent(self, m): permanent_with_error = xq.estimate_permanent_cx(m, self._gurvits_iterations) return permanent_with_error[0] def prob_amplitude_with_error(self, output_state: FockState) -> tuple[complex, float]: """Computes the estimation of the probability amplitude along with an estimation of the 99% sure error bound.""" m = self._compute_submatrix(output_state) permanent_with_error = xq.estimate_permanent_cx(m, self._gurvits_iterations) normalization_coeff = math.sqrt(output_state.prodnfact() * self._input_state.prodnfact()) return (permanent_with_error[0]/normalization_coeff, permanent_with_error[1]/normalization_coeff) \ if m.size > 1 else (m[0, 0], 0) def probability_confidence_interval(self, output_state: FockState) -> list[float]: """Computes the 99% confidence interval for the true value of the probability.""" mean, err = self.prob_amplitude_with_error(output_state) min_prob = max((abs(mean) - err) ** 2, 0) max_prob = min((abs(mean) + err) ** 2, 1) return [min_prob, max_prob] ================================================ FILE: perceval/backends/_slap.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import exqalibur as xq from perceval.utils import FockState, BSDistribution, StateVector, NoisyFockState, SVDistribution, Matrix from perceval.components import (ACircuit, AFFConfigurator, FFCircuitProvider, Experiment, IDetector, Processor, DetectionType, AComponent, Circuit, Barrier, FFConfigurator) from ._abstract_backends import AStrongSimulationBackend, IFFBackend class SLAPBackend(AStrongSimulationBackend, IFFBackend): def __init__(self, mask=None): super().__init__() self._slap = xq.SLAP() if mask is not None: self.set_mask(mask) def set_circuit(self, circuit: ACircuit): super().set_circuit(circuit) # Computes circuit unitary as _umat self._slap.reset_feed_forward() self._slap.set_unitary(self._umat) def _init_mask(self): super()._init_mask() if self._mask: self._slap.set_mask(self._mask) else: self._slap.reset_mask() def prob_amplitude(self, output_state: FockState) -> complex: istate = self._input_state all_pa = self._slap.all_prob_ampli(istate) if self._mask: return all_pa[xq.FSArray(self._input_state.m, self._input_state.n, self._mask).find(output_state)] else: return all_pa[xq.FSArray(self._input_state.m, self._input_state.n).find(output_state)] def prob_distribution(self) -> BSDistribution: return self._slap.prob_distribution(self._input_state) @property def name(self) -> str: return "SLAP" def all_prob(self, input_state: FockState = None): if input_state is not None: self.set_input_state(input_state) else: input_state = self._input_state return self._slap.all_prob(input_state) def evolve(self) -> StateVector: istate = self._input_state all_pa = self._slap.all_prob_ampli(istate) res = StateVector() for output_state, pa in zip(self._get_iterator(self._input_state), all_pa): res += output_state * pa return res @staticmethod def can_simulate_feed_forward(components, input_state, detectors = None) -> bool: # Conditions: # - No change of number of photons outside the backend # - Detectors of measured modes are PNR, # - No NoisyFockState as input (as they require merging) # - Configurators don't configure on modes above them, nor do they point to heralded or non-unitary experiments from perceval import Processor, Experiment if isinstance(input_state, NoisyFockState): return False if isinstance(input_state, tuple): source = input_state[0] if source.partially_distinguishable: return False if isinstance(input_state, SVDistribution): for state in input_state: if isinstance(state, NoisyFockState): return False def accept(p): if isinstance(p, Processor): p = p.experiment if isinstance(p, Experiment) and (p.heralds or p.in_heralds or not p.is_unitary): return False return True measured_modes = set() for i, (r, c) in enumerate(components): if isinstance(c, AFFConfigurator): if c.circuit_offset < 0: return False if isinstance(c, FFCircuitProvider): if not accept(c.default_circuit): return False if not all(accept(p) for p in c.circuit_map.values()): return False measured_modes.update(r) if detectors is not None: for m in measured_modes: if detectors[m] is not None and detectors[m].type != DetectionType.PNR: return False return True def set_feed_forward(self, components: list[tuple[tuple, AComponent]], m: int) -> None: """ :param components: A list of placed components containing feed-forward such that: - None of the configurators configures modes above it - None of the configurators points to a heralded or non-unitary experiment :param m: The size of the circuit :return: """ main_unitary = Circuit(m) config_map: dict[FockState, Matrix] = None maps = [] for r, c in components: if isinstance(c, Experiment): assert c.is_unitary c = c.unitary_circuit() if isinstance(c, IDetector) or isinstance(c, Barrier): continue if isinstance(c, ACircuit): if not config_map: main_unitary.add(r, c) else: maps.append((c.compute_unitary(), r[0])) continue elif not isinstance(c, AFFConfigurator): raise ValueError("Received non-unitary components") config_modes = c.config_modes(r) default_circuit = c.default_circuit if isinstance(c, FFCircuitProvider): config_map = {} for measure, sub_c in c.circuit_map.items(): if isinstance(sub_c, Experiment): sub_c = sub_c.unitary_circuit() elif isinstance(sub_c, Processor): sub_c = sub_c.linear_circuit() config_map[measure] = sub_c.compute_unitary() elif isinstance(c, FFConfigurator): config_map = {measure: c.configure(measure).compute_unitary() for measure in c._configs.keys()} maps.append(xq.ConfiguratorMap(r[0], r[-1], config_modes[0], config_map, default_circuit.compute_unitary())) self.set_circuit(main_unitary) for mp in maps: self._slap.add_feed_forward_config(mp) ================================================ FILE: perceval/backends/_slos.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from ._abstract_backends import AStrongSimulationBackend from perceval.utils import Matrix, FockState, BSDistribution, StateVector from perceval.utils.logging import get_logger, channel import exqalibur as xq import math class _Path: """A `Path` is the minimal computing graph for covering a set of input states""" def __init__(self, n, m, states, targets, backend): self._n = n self._m = m self._backend = backend self.coefs = Matrix.zeros((backend._mk_l[n], 1), use_symbolic=self._backend._symb) if n == 0: self.coefs.fill(1) if targets is None: targets = [list(state) for state in states] self._targets = [] self._states = [] self._children = {} for t, s in zip(targets, states): if sum(t) == 0: backend._state_mapping[s] = self else: self._targets.append(t) self._states.append(s) self._decompose() def _decompose(self): targets = self._targets states = self._states while targets: counts = [0] * len(targets[0]) for one_target in targets: counts = [x + y for (x, y) in zip(counts, one_target)] max_mode = max(counts) max_index = counts.index(max_mode) current_targets = [] current_states = [] new_targets = [] new_states = [] for one_target, one_state in zip(targets, states): if one_target[max_index]: one_target[max_index] -= 1 current_targets.append(one_target) current_states.append(one_state) else: new_targets.append(one_target) new_states.append(one_state) self._children[max_index] = _Path(self._n + 1, self._m, current_states, current_targets, self._backend) targets = new_targets states = new_states def compute(self, u, parent_coefs: Matrix = None, mk: int = None): r"""Given the precompiled compute path, update all the coefficients""" if parent_coefs is not None: if self._backend._symb: self.coefs.fill(0) for parent_idx, coef_parent in enumerate(parent_coefs): for j in range(self._m): idx = self._backend._fsms[self._n].get(parent_idx, j) if idx != xq.npos: self.coefs[idx] += coef_parent * u[j, mk] else: self._backend._fsms[self._n].compute_slos_layer(u, self._m, mk, self.coefs, parent_coefs) for mk, child in self._children.items(): child.compute(u, self.coefs, mk) class SLOSBackend(AStrongSimulationBackend): def __init__(self, mask=None, use_symbolic=False): super().__init__() self._reset() self._symb = use_symbolic if mask is not None: self.set_mask(mask) @property def name(self) -> str: return "SLOS" def _reset(self): self._fsms: list[xq.FSMap] = [[]] self._fsas: dict[int, xq.FSArray] = {} self._mk_l: list[int] = [1] self._path_roots: list[_Path] = [] self._state_mapping: dict[FockState, _Path] = {} self._mask: xq.FSMask = None self.clear_iterator_cache() def _compute_path(self, umat): for path in self._path_roots: path.compute(umat) def set_circuit(self, circuit): previous_circuit = self._circuit assert not circuit.requires_polarization, "Circuit must not contain polarized components" self._input_state = None self._circuit = circuit self._umat = circuit.compute_unitary(use_symbolic=self._symb) if self._path_roots and previous_circuit.m == circuit.m: # Use the previously deployed paths to store the new circuit's coefs get_logger().debug("SLOS: compute coefficients keeping the previous path", channel.general) self._compute_path(self._umat) else: self._reset() def set_input_state(self, input_state: FockState): super().set_input_state(input_state) self.preprocess([input_state]) def clear_mask(self): super().clear_mask() self._reset() def _deploy(self, input_list: list[FockState]): # allocate the fsas and fsms for covering all the input_states respecting possible mask # after calculation, we only need to keep fsa for input_state n # during calculation we need to keep current fsa and previous fsa m = self._circuit.m current_fsa = xq.FSArray(m, 0) if len(self._fsas) == 0 else self._fsas[max(self._fsas.keys())] for input_state in input_list: n = input_state.n if n < len(self._fsms) and n not in self._fsas: # we are missing the intermediate states - let us retrieve/load it back current_fsa = xq.FSArray(m, n, self._mask) if self._mask else xq.FSArray(m, n) for k in range(len(self._fsms), n + 1): fsa_n_m1 = current_fsa current_fsa = xq.FSArray(m, k, self._mask) if self._mask else xq.FSArray(m, k) self._mk_l.append(current_fsa.count()) self._fsms.append(xq.FSMap(current_fsa, fsa_n_m1, True)) if n not in self._fsas: self._fsas[n] = current_fsa def preprocess(self, input_list: list[FockState]) -> bool: # now check if we have a path for the input states found_new = False for input_state in input_list: found_new = (input_state not in self._state_mapping) if found_new: break if not found_new: return False get_logger().debug("SLOS: deploy a new path and compute coefficients", channel.general) self._deploy(input_list) # build the necessary fsa/fsms new_path = _Path(0, self._circuit.m, input_list, None, self) new_path.compute(self._umat) self._path_roots.append(new_path) return True def prob_amplitude(self, output_state: FockState) -> complex: if self._input_state.n != output_state.n: return complex(0) output_idx = self._fsas[output_state.n].find(output_state) assert output_idx != xq.npos result = self._state_mapping[self._input_state].coefs[output_idx, 0] * math.sqrt(output_state.prodnfact() / self._input_state.prodnfact()) return result if self._symb else complex(result) def prob_distribution(self) -> BSDistribution: istate = self._input_state c = self._state_mapping[istate].coefs.reshape(self._fsas[istate.n].count()) c = abs(c) ** 2 / istate.prodnfact() xq.all_prob_normalize_output(c, self._fsas[istate.n]) bsd = BSDistribution() for output_state, probability in zip(self._get_iterator(istate), c): bsd.add(output_state, probability) return bsd def all_prob(self, input_state: FockState = None): """SLOS specific signature, to enhance optimization in some computations""" if input_state is not None: self.set_input_state(input_state) else: input_state = self._input_state c = self._state_mapping[input_state].coefs.reshape(self._fsas[input_state.n].count()) c = abs(c)**2 / self._input_state.prodnfact() xq.all_prob_normalize_output(c, self._fsas[input_state.n]) return c.tolist() def evolve(self) -> StateVector: istate = self._input_state c = self._state_mapping[istate].coefs.reshape(self._fsas[istate.n].count()) res = StateVector() iprodnfact = istate.prodnfact() for output_state, pa in zip(self._fsas[istate.n], c): res += output_state * (pa * math.sqrt(output_state.prodnfact() / iprodnfact)) return res ================================================ FILE: perceval/components/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .abstract_component import AComponent from .abstract_processor import AProcessor, ProcessorType from .experiment import Experiment from .compiled_circuit import CompiledCircuit from .linear_circuit import Circuit, ACircuit from .generic_interferometer import GenericInterferometer from .processor import Processor from .source import Source from ._pauli import (PauliType, PauliEigenStateType, get_pauli_eigen_state_prep_circ, get_pauli_basis_measurement_circuit, get_pauli_gate, get_pauli_eigenvector_matrix, get_pauli_eigenvectors) from .tomography_exp_configurer import processor_circuit_configurator from ._decompose_perms import decompose_perms from .port import APort, Port, Herald, PortLocation, get_basic_state_from_ports from .detector import IDetector, DetectionType, Detector, BSLayeredPPNR, get_detection_type, check_heralds_detectors from .unitary_components import BSConvention, BS, PS, WP, HWP, QWP, PR, Unitary, PERM, PBS, Barrier from .non_unitary_components import TD, LC from .component_catalog import Catalog from ._mode_connector import ModeConnector, UnavailableModeException from .feed_forward_configurator import AFFConfigurator, FFCircuitProvider, FFConfigurator catalog = Catalog('perceval.components.core_catalog') ================================================ FILE: perceval/components/_decompose_perms.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .unitary_components import PERM from .linear_circuit import Circuit def decompose_perms(circuit: Circuit, merge: bool=True) -> Circuit: """ Sweeps through a Circuit to find any complex n-mode PERM components to output an equivalent circuit containing only 2-mode PERMS """ decomp_c = Circuit(circuit.m, name="Decomposed Circuit") for r, c in circuit: if isinstance(c, PERM): new_c = c.break_in_2_mode_perms() decomp_c.add(r, new_c, merge=merge) else: decomp_c.add(r, c) return decomp_c ================================================ FILE: perceval/components/_mode_connector.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.utils.logging import get_logger, channel from .abstract_component import AComponent from .unitary_components import PERM class UnavailableModeException(Exception): def __init__(self, mode: int | list[int] | tuple[int], reason: str = None): because = '' if reason: because = f' because: {reason}' super().__init__(f"Mode(s) {mode} not available{because}") class InvalidMappingException(Exception): def __init__(self, mapping: dict | list, reason: str = None): because = '' if reason: because = f' because: {reason}' super().__init__(f"Invalid mapping ({mapping}){because}") class ModeConnector: """ Resolves a mapping, supporting multiple syntaxes, to connect two objects. The left object must be an Experiment The right object can be an Experiment or a (unitary or non-unitary) component """ def __init__(self, left_experiment, right_obj, mapping): """ :param left_experiment: any experiment on which to plug `right_obj` :param right_obj: the component or processor to plug on the right of `left_experiment` :param mapping: the user mapping defining the plugging rules (see resolve method doc for more info) """ from .abstract_processor import AProcessor if isinstance(left_experiment, AProcessor): left_experiment = left_experiment.experiment self._le = left_experiment self._ro = right_obj # Can either be a component or a processor self._r_is_component = isinstance(right_obj, AComponent) # False means it is an Experiment self._map = mapping self._l_port_names = None self._r_port_names = None self._n_modes_to_connect = right_obj.m if self._r_is_component else right_obj.m_in def _mapping_type_checks(self): assert isinstance(self._map, dict), f"Mapping should be a Python dictionary, got {type(self._map)}" for k, v in self._map.items(): assert isinstance(k, int) or isinstance(k, str), f"Mapping keys supported types are str and int, found {k}" if self._r_is_component: assert isinstance(v, int) or isinstance(v, list), \ "Mapping values must all be integers when the right object is not a processor" else: assert isinstance(v, int) or isinstance(v, str) or isinstance(v, list), \ f"Mapping values supported types are str and int, found {v}" def _get_ordered_rmodes(self): """ Returns ordered mode of interest index (i.e. ignores heralded modes in case the right object is a Processor) """ if self._r_is_component: return list(range(self._n_modes_to_connect)) r_list = list(range(self._ro.circuit_size)) return [x for x in r_list if x not in self._ro.in_heralds.keys()] def resolve(self) -> dict[int, int]: """ Resolves mode mapping (self._map) and checks if it is consistent. :return: the resolved mapping containing mode indexes self._map can be an integer, a list or a dictionary. Case int: Creates a dictionary { mapping: 0, mapping+1: 1, ..., mapping+n: n } Case list: Creates a dictionary { mapping[0]: 0, mapping[1]: 1, ..., mapping[n]: n} Case dict: keys and values can either be integers or strings. If strings, it expects port names of the same size. Consistency checks: - The input map key and value types are checked. - Each key/value pair size should match. For instance, 'data': [1,2,3] will fail if 'data' port length is 2 - The constructed mapping must be the right size (= length of modes to connect of right object) - Resolved indexes must not be negative - All left output modes used in the mapping must be connectible - Duplicates are checked """ # Handle int input case if isinstance(self._map, int): get_logger().debug(f"Resolve mode mapping from int {self._map}", channel.general) map_begin = self._map self._map = {} r_list = self._get_ordered_rmodes() herald_offset = 0 le_heralds = self._le.heralds for i in range(self._n_modes_to_connect): pos = map_begin + i + herald_offset while pos in le_heralds: herald_offset += 1 pos += 1 self._map[pos] = r_list[i] self._check_consistency() return self._map # Handle list input case if isinstance(self._map, list) or isinstance(self._map, tuple): get_logger().debug("Resolve mode mapping from a list", channel.general) map_keys = self._map map_values = self._get_ordered_rmodes() if len(map_keys) != len(map_values): raise InvalidMappingException(map_keys, f"input list size is expected to be {len(map_values)}") self._map = {k: v for k, v in zip(map_keys, map_values)} self._check_consistency() return self._map # Handle dict input case self._mapping_type_checks() result = {} for k, v in self._map.items(): if isinstance(k, int) and isinstance(v, int): result[k] = v elif isinstance(k, str): # Mapping between port name and index l_idx = self._resolve_port_left(k) r_idx = [] if l_idx is None: raise InvalidMappingException(self._map, f"port '{k}' was not found in processor") if isinstance(v, int) and len(l_idx) == 1: result[l_idx[0]] = v elif isinstance(v, list): r_idx = v else: # str r_idx = self._resolve_port_right(v) if r_idx is None: raise InvalidMappingException(self._map, f"port '{v}' was not found in processor") if len(l_idx) != len(r_idx): raise InvalidMappingException(self._map, f"Unable to resolve '{k}: {v}' - imbalanced ports") for i in range(len(l_idx)): result[l_idx[i]] = r_idx[i] self._map = result self._check_consistency() return self._map def _check_consistency(self): """ Checks the mapping consistency """ if len(self._map) != self._n_modes_to_connect: raise InvalidMappingException(self._map) min_out = min(self._map.keys()) if min_out < 0: raise UnavailableModeException(min_out) for m_out, m_in in self._map.items(): if not self._le.is_mode_connectible(m_out): raise UnavailableModeException(m_out) m_in = self._map.values() if len(m_in) != len(list(dict.fromkeys(m_in))): # suppress duplicates and check length raise InvalidMappingException(self._map) def _resolve_port_left(self, name: str): """ Resolves mode indexes from an output port name of the left processor """ if self._l_port_names is None: self._l_port_names = self._le.out_port_names count = self._l_port_names.count(name) if count == 0: return None pos = self._l_port_names.index(name) res = [] for i in range(count): res.append(pos) pos += 1 return res def _resolve_port_right(self, name: str): """ Resolves mode indexes from an input port name of the right object (which has to be a processor) """ assert not self._r_is_component, "Port names are only available on processors" if self._r_port_names is None: self._r_port_names = self._ro.in_port_names count = self._r_port_names.count(name) if count == 0: return None pos = self._r_port_names.index(name) res = [] for i in range(count): res.append(pos) pos += 1 return res def add_heralded_modes(self, mapping): """ Add heralded mode mapping to an existing mapping """ if self._r_is_component: get_logger().warn("Right object is not a processor, thus doesn't contain heralded modes", channel.user) return 0 other_herald_pos = list(self._ro.in_heralds.keys()) new_mode_index = self._le.circuit_size for pos in other_herald_pos: mapping[new_mode_index] = pos new_mode_index += 1 return new_mode_index - self._le.circuit_size @staticmethod def generate_permutation(mode_mapping: dict[int, int]): """ Generate a PERM component given an already resolved mode mapping Returns a tuple containing: - The mode range occupied by the PERM component - The PERM component or None if no PERM is needed (no swap in the mapping) """ m_keys = list(mode_mapping.keys()) min_m = min(m_keys) max_m = max(m_keys) missing_modes = [x for x in list(range(min_m, max_m + 1)) if x not in m_keys] for mm in missing_modes: mode_mapping[mm] = max(mode_mapping.values()) + 1 perm_modes = list(range(min_m, min_m + len(mode_mapping))) perm_vect = [mode_mapping[i] for i in sorted(mode_mapping.keys())] if perm_vect == list(range(len(perm_modes))): return perm_modes, None # No need for a permutation, modes are already sorted return perm_modes, PERM(perm_vect) def compose_lists(self, mode_mapping: dict[int, int], l: list, r: list): """ Takes a list of object from the left experiment and the right experiment, as well as the mode mapping. Applies the mapping to the list of the left experiment then adds the members of the list of the right experiment so that the resulting list should be given to the composed experiment (in the case no inverse permutation is added afterward) """ res = [None] * self._le.circuit_size mode_0 = min(mode_mapping) for i, o in enumerate(l): if i in mode_mapping: mode = mode_mapping[i] + mode_0 else: mode = i res[mode] = o for i, o in enumerate(r): mode = mode_0 + i res[mode] = o return res ================================================ FILE: perceval/components/_pauli.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np from enum import Enum from .linear_circuit import Circuit from .unitary_components import BS, PS, PERM from perceval.utils import Matrix class PauliType(Enum): """ Enumeration of different Pauli (gates/operators) Types + Identity. """ # Order of members important I = 0 X = 1 Y = 2 Z = 3 class PauliEigenStateType(Enum): """ Enumeration of different eigenstates of Pauli operators. """ # Order of members important Zm = 0 # Pauli eigen-state Z- : |1> Zp = 1 # Pauli eigen-state Z+ : |0> Xp = 2 # Pauli eigen-state X+ : |+> Xm = 3 # Pauli eigen-state X- : |-> Yp = 4 # Pauli eigen-state Y+ : |i+> Ym = 5 # Pauli eigen-state Y- : |i-> def get_pauli_eigen_state_prep_circ(pauli_type: PauliEigenStateType) -> Circuit: """ Generates a 2-mode LO circuit to prepare the logical states in one of the eigen states of Pauli Z_p : |0>, Z_m : |1>, X_p : |+>, X_m : |->, Y_p : |i+>, and Y_m : |i-> :param pauli_type: PauliType :return: 2 mode perceval circuit """ if pauli_type == PauliEigenStateType.Zm: return Circuit(2, name="Zm State Preparer") elif pauli_type == PauliEigenStateType.Zp: return Circuit(2, name="Zp State Preparer") // PERM([1, 0]) elif pauli_type == PauliEigenStateType.Xp: return Circuit(2, name="Xp State Preparer") // BS.H() elif pauli_type == PauliEigenStateType.Xm: return Circuit(2, name="Xm State Preparer") // PERM([1, 0]) // BS.H() elif pauli_type == PauliEigenStateType.Yp: return Circuit(2, name="Yp State Preparer") // BS.H() // (1, PS(np.pi / 2)) elif pauli_type == PauliEigenStateType.Ym: return Circuit(2, name="Ym State Preparer") // PERM([1, 0]) // BS.H() // (1, PS(np.pi / 2)) else: raise NotImplementedError(f"{pauli_type}") def get_pauli_gate(pauli_type: PauliType): """ Uses the PauliType to choose and compute the matrix corresponding Pauli (gates) operators (I,X,Y,Z). :param pauli_type: PauliType :return: 2x2 unitary and hermitian array """ if pauli_type == PauliType.I: return Matrix.eye(2) elif pauli_type == PauliType.X: return Matrix([[0, 1], [1, 0]]) elif pauli_type == PauliType.Y: return Matrix([[0, -1j], [1j, 0]]) elif pauli_type == PauliType.Z: return Matrix([[1, 0], [0, -1]]) else: raise NotImplementedError(f"{pauli_type}") def get_pauli_eigenvectors(pauli_type) -> list: if pauli_type == PauliEigenStateType.Zm: return np.array([[1], [0]], dtype=np.cdouble) elif pauli_type == PauliEigenStateType.Zp: return np.array([[0], [1]], dtype=np.cdouble) elif pauli_type == PauliEigenStateType.Xp: return (1 / np.sqrt(2)) * np.array([[1], [1]], dtype=np.cdouble) elif pauli_type == PauliEigenStateType.Xm: return (1 / np.sqrt(2)) * np.array([[1], [-1]], dtype=np.cdouble) elif pauli_type == PauliEigenStateType.Yp: return (1 / np.sqrt(2)) * np.array([[1], [1j]], dtype=np.cdouble) elif pauli_type == PauliEigenStateType.Ym: return (1 / np.sqrt(2)) * np.array([[1], [-1j]], dtype=np.cdouble) else: raise NotImplementedError(f"{pauli_type}") def get_pauli_eigenvector_matrix(pauli_eigenv) -> np.ndarray: if pauli_eigenv == PauliType.X: return (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=np.cdouble) elif pauli_eigenv == PauliType.Y: return (1 / np.sqrt(2)) * np.array([[1, 1], [1j, -1j]], dtype=np.cdouble) else: return np.eye((2), dtype=np.cdouble) def get_pauli_basis_measurement_circuit(pauli_type: PauliType) -> Circuit: """ Uses the PauliType to choose and create LO measurement circuits in Pauli Basis (I,X,Y,Z). Equivalent to measuring eigenstates of the 1-qubit Pauli gates :param pauli_type: PauliType :return: a 2-modes circuit """ assert isinstance(pauli_type, PauliType), f"Wrong type, expected Pauli, got {type(pauli_type)}" if pauli_type == PauliType.I: return Circuit(2, name="I Measurer") elif pauli_type == PauliType.X: return Circuit(2, name="Pauli X Measurer") // BS.H() elif pauli_type == PauliType.Y: return Circuit(2, name="Pauli Y Measurer") // BS.Rx(theta=np.pi/2, phi_bl=np.pi, phi_br=-np.pi/2) elif pauli_type == PauliType.Z: return Circuit(2, name="Pauli Z Measurer") else: raise NotImplementedError(f"{pauli_type}") ================================================ FILE: perceval/components/abstract_component.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import ABC from collections.abc import Iterable import sympy as sp import copy from perceval.utils.parameter import Parameter, Expression class AComponent(ABC): DEFAULT_NAME = None def __init__(self, m: int, name: str = None): self._m = m self._name = name @property def m(self) -> int: return self._m @property def name(self) -> str: """Returns component name""" if self._name is None: return self.DEFAULT_NAME return self._name @name.setter def name(self, new_name: str = None) -> None: """Sets new component name""" self._name = new_name def is_composite(self) -> bool: """ :return: True if the component is itself composed of subcomponents """ return False class AParametrizedComponent(AComponent): def __init__(self, m: int, name: str = None): super().__init__(m, name) self._params = {} self._vars = {} @property def vars(self) -> dict[str, Parameter]: """ :return: A dictionary mapping parameter names to parameters for all variable parameters of the circuit """ return {p.name: p for p in self._params.values() if not p.fixed} def assign(self, assign: dict[str, float | int] = None): """ Assign values to parameters referenced in assign :param assign: A dictionary mapping parameter_name -> value. Set the value to the parameter whose name is parameter_name for each key of the dictionary. :raise KeyError: If parameter_name is not an existing variable parameter name of the circuit. """ if assign is None: return vs = self.vars if isinstance(assign, dict): for k, v in assign.items(): vs[k].set_value(v) @property def defined(self) -> bool: """ check if all parameters of the circuit are fully defined """ for _, p in self._params.items(): if not p.defined: return False return True @property def params(self) -> Iterable[str]: """ :return: a list of all variable parameter names in the component """ return self._params.keys() def param(self, param_name: str) -> Parameter: """Extract a `Parameter` object from its name :param param_name: The name of the parameter :return: A `Parameter` object """ if param_name not in self._params: raise KeyError(f"Parameter {param_name} is not defined in this circuit") return self._params[param_name] def get_parameters(self, all_params: bool = False, expressions = False) -> list[Parameter]: """Return the parameters of the component :param all_params: if False, only returns the variable parameters :param expressions: if True, returns Expressions and parameters embedded in circuit components. If False, returns the raw parameters that make up the expressions only. Default False. :return: the list of parameters """ param_list = [] for param in self._params.values(): if all_params or not param.fixed: if isinstance(param, Expression): if expressions: if param not in param_list: param_list.append(param) else: for p in param.parameters: if p not in param_list: param_list.append(p) elif param not in param_list: param_list.append(param) return param_list def reset_parameters(self) -> None: for v in self._params.values(): v.reset() def _set_parameter(self, name: str, p: Parameter | float, min_v: float | None, max_v: float | None, periodic: bool = True) -> Parameter: """ Define a parameter for the circuit, it can be an existing parameter that we recycle updating min/max value or a parameter defined by a value that we create on the fly :param name: parameter name :param p: parameter instance or numerical value :param min_v: minimum numerical value (can be None) :param max_v: maximum numerical value (can be None) :param periodic: True if the value is periodic (e.g. for an angle) :return: The corresponding `Parameter` object """ if isinstance(p, Parameter): if min_v is not None: if p.min is None or min_v > p.min: p.min = float(min_v) if max_v is not None: if p.max is None or max_v < p.max: p.max = float(max_v) if p.name in self._vars: if p is not self._vars[p.name]: raise RuntimeError("two parameters with the same name in the circuit") if periodic is not None: p.set_periodic(periodic) self._vars[p.name] = p else: p = Parameter(value=p, name=name, min_v=min_v, max_v=max_v, periodic=periodic) self._params[name] = p return p def _populate_parameters(self, out_parameters: dict, pname: str, default_value: float = None): """ Populate an in/out dictionary with a {parameter name: best value for display} couple, if needed. A value equal to the optional default value will not be injected in the dictionary. :param out_parameters: out dictionary, where key/value pairs are added. :param pname: parameter name to consider, in the component definition. e.g. to retrieve "phi0" from PS(phi=P("phi0")), ask for pname="phi", as it's the parameter name for a PS. :param default_value: optional default numerical value. None means no default value. """ p = self._params[pname] if p.defined: if default_value is None or p._value != default_value: v = p._value if isinstance(v, sp.Expr): out_parameters[pname] = str(v) else: if p._is_expression: v = float(p) # Re-evaluate the expression value if default_value is None or abs(v - float(default_value)) > 1e-6: out_parameters[pname] = v else: out_parameters[pname] = self._params[pname].name def get_variables(self): return {} def copy(self): return copy.deepcopy(self) ================================================ FILE: perceval/components/abstract_processor.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing import copy from abc import ABC, abstractmethod from enum import Enum from perceval.utils import BasicState, FockState, Parameter, PostSelect, LogicalState, NoiseModel, SVDistribution, StateVector from perceval.utils.logging import get_logger, channel from .abstract_component import AComponent from .detector import DetectionType from .experiment import Experiment from .linear_circuit import Circuit, ACircuit from .port import PortLocation, APort class ProcessorType(Enum): SIMULATOR = 1 PHYSICAL = 2 class AProcessor(ABC): def __init__(self, experiment = None): self.experiment = experiment or Experiment() self._parameters: dict[str, any] = {} @property def experiment(self) -> Experiment: return self._experiment @experiment.setter def experiment(self, experiment: Experiment): self._experiment = experiment experiment.add_observers(self._circuit_change_observer, self._noise_changed_observer, self._input_changed_observer) @abstractmethod def _circuit_change_observer(self, new_component = None): pass @abstractmethod def _noise_changed_observer(self): pass def _input_changed_observer(self): pass @property def name(self) -> str: return self.experiment.name @name.setter def name(self, name: str): self.experiment.name = name @property @abstractmethod def type(self) -> ProcessorType: pass @property @abstractmethod def is_remote(self) -> bool: pass @property def specs(self): return dict() def set_parameters(self, params: dict[str, any]): for key, value in params.items(): self.set_parameter(key, value) def set_parameter(self, key: str, value: any): if not isinstance(key, str): raise TypeError(f"A parameter name has to be a string (got {type(key)})") self._parameters[key] = value @property def parameters(self): return self._parameters def clear_parameters(self): self._parameters = {} def clear_input_and_circuit(self, new_m=None): self.experiment.clear_input_and_circuit(new_m) @property def _min_detected_photons_filter(self): return self.experiment.min_photons_filter def min_detected_photons_filter(self, n: int): r""" Sets-up a state post-selection on the number of detected photons. With threshold detectors, this will actually filter on "click" count. :param n: Minimum expected photons. Does not take heralded modes into account. This post-selection has an impact on the output physical performance """ self.experiment.min_detected_photons_filter(n) @property def input_state(self): return self.experiment.input_state @property def noise(self): return self.experiment.noise @noise.setter def noise(self, nm: NoiseModel): self.experiment.noise = nm @property @abstractmethod def available_commands(self) -> list[str]: pass def remove_heralded_modes(self, s: FockState) -> FockState: if self.heralds: s = s.remove_modes(list(self.heralds.keys())) return s @property def post_select_fn(self): return self.experiment.post_select_fn def set_postselection(self, postselect: PostSelect): r""" Set a logical post-selection function. Along with the heralded modes, this function has an impact on the logical performance of the processor :param postselect: Sets a post-selection function. Its signature must be `func(s: BasicState) -> bool`. If None is passed as parameter, removes the previously defined post-selection function. """ self.experiment.set_postselection(postselect) def clear_postselection(self): self.experiment.clear_postselection() @abstractmethod def compute_physical_logical_perf(self, value: bool): pass def _state_selected(self, state: BasicState) -> bool: """ Computes if the state is selected given heralds and post selection function """ # TODO: see what to do with this method for m, v in self.heralds.items(): if state[m] != v: return False if self.experiment.post_select_fn is not None: return self.experiment.post_select_fn(state) return True def set_circuit(self, circuit: ACircuit) -> AProcessor: r""" Removes all components and replace them by the given circuit. :param circuit: The circuit to start the processor with :return: Self to allow direct chain this with .add() """ self.experiment.set_circuit(circuit) return self def add(self, mode_mapping, component, keep_port: bool = True) -> AProcessor: """ Add a component to the processor (unitary or non-unitary). :param mode_mapping: Describe how the new component is connected to the existing processor. Can be: * an int: composition uses consecutive modes starting from `mode_mapping` * a list or a dict: describes the full mapping of length the input mode count of `component` :param component: The component to append to the processor. Can be: * A unitary circuit * A non-unitary component * A processor * A detector :param keep_port: if True, saves `self`'s output ports on modes impacted by the new component, otherwise removes them. Adding a component on non-ordered, non-consecutive modes computes the right permutation (PERM component) which fits into the existing processor and the new component. Example: >>> p = Processor("SLOS", 6) >>> p.add(0, BS()) # Modes (0, 1) connected to (0, 1) of the added beam splitter >>> p.add([2,5], BS()) # Modes (2, 5) of the processor's output connected to (0, 1) of the added beam splitter >>> p.add({2:0, 5:1}, BS()) # Same as above """ self.experiment.add(mode_mapping, component, keep_port) return self @property def detectors(self): return self.experiment.detectors def add_herald(self, mode: int, expected: int, name: str = None, location: PortLocation = PortLocation.IN_OUT) -> AProcessor: r""" Add a heralded mode :param mode: Mode index of the herald :param expected: number of expected photon as input and/or output on the given mode :param name: Herald port name. If none is passed, the name is auto-generated :param location: Port location of the herald (input, output or both) """ self.experiment.add_herald(mode, expected, name, location) return self @property def components(self): return self.experiment.components @property def m(self) -> int: """ :return: The number of modes that are free (non-heralded) on the output """ return self.experiment.m @property def m_in(self) -> int: """ :return: The number of modes that are free (non-heralded) on the input, that must be specified when using self.with_input() """ return self.experiment.m_in @m.setter def m(self, value: int): self.experiment.m = value @property def circuit_size(self) -> int: r""" :return: Total size of the enclosed circuit (i.e. self.m + heralded mode count) """ return self.experiment.circuit_size def linear_circuit(self, flatten: bool = False) -> Circuit: """ Creates a linear circuit from internal components, if all internal components are unitary. :param flatten: if True, the component recursive hierarchy is discarded, making the output circuit "flat". """ return self.experiment.unitary_circuit(flatten=flatten) def non_unitary_circuit(self, flatten: bool = False) -> list[tuple[tuple, AComponent]]: return self.experiment.non_unitary_circuit(flatten=flatten) def get_circuit_parameters(self) -> dict[str, Parameter]: return self.experiment.get_circuit_parameters() @property def out_port_names(self): r""" :return: A list of the output port names. Names are repeated for ports connected to more than one mode """ return self.experiment.out_port_names @property def in_port_names(self): r""" :return: A list of the input port names. Names are repeated for ports connected to more than one mode """ return self.experiment.in_port_names def add_port(self, m, port: APort, location: PortLocation = PortLocation.IN_OUT): self.experiment.add_port(m, port, location) return self def remove_port(self, m, location: PortLocation = PortLocation.IN_OUT): self.experiment.remove_port(m, location) return self def get_input_port(self, mode): return self.experiment.get_input_port(mode) def get_output_port(self, mode): return self.experiment.get_output_port(mode) @property def detection_type(self) -> DetectionType: return self.experiment.detection_type @property def heralds(self): """ :return: A dictionary {mode: expected_count} describing the heralds on the output """ return self.experiment.heralds @property def in_heralds(self): """ :return: A dictionary {mode: expected_count} describing the heralds on the input """ return self.experiment.in_heralds def check_input(self, input_state: FockState): r"""Check if a Fock state input matches with the current processor configuration""" self.experiment.check_input(input_state) def check_min_detected_photons_filter(self): if self._min_detected_photons_filter is None: if (not self.is_remote and self._source is not None and self._source.is_perfect() and isinstance(self.input_state, BasicState)): # Automatically set the min_detected_photons_filter for perfect sources of local processors if not set self.min_detected_photons_filter(self.input_state.n - sum(self.heralds.values())) else: raise ValueError("The value of min_detected_photons is not set." " Use the method processor.min_detected_photons_filter(value).") def with_input(self, input_state: BasicState | LogicalState | StateVector | SVDistribution): """ Simulates plugging the photonic source on certain modes and turning it on. Computes the input probability distribution :param input_state: Expected input BasicState, StateVector or SVDistribution of length `self.m` (heralded modes are managed automatically), or a LogicalState with length 'number of non-herald ports or port-free modes'. The properties of the source will alter the input state for BasicState and LogicalState inputs. A perfect source always delivers the expected state as an input. Imperfect ones won't. """ self.experiment.with_input(input_state) def flatten(self, max_depth = None) -> list[tuple]: """ List all the components in the processor where recursive circuits have been flattened. :param max_depth: The maximum depth of recursion. The remaining sub-circuits at this depth are listed as a component. """ return self.experiment.flatten(max_depth) ================================================ FILE: perceval/components/compiled_circuit.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from packaging.version import Version from perceval.components.linear_circuit import ACircuit from perceval.utils.matrix import Matrix class CompiledCircuit(ACircuit): def __init__(self, name: str, template_or_size: ACircuit | int, parameters: list[float], version: Version | None = None): m = template_or_size if isinstance(template_or_size, int) else template_or_size.m template = template_or_size if isinstance(template_or_size, ACircuit) else None super().__init__(m, name) self.version = version self.parameters = parameters self.template = template if self.template: assert len(self.template.params) == len(self.parameters), "Incorrect BasicState size" def _compute_unitary(self, assign: dict = None, use_symbolic: bool = False) -> Matrix: """Compute the unitary matrix corresponding to the current circuit :param assign: assign values to some parameters :param use_symbolic: if the matrix should use symbolic calculation :return: the unitary matrix, will be a :class:`~perceval.utils.matrix.MatrixS` if symbolic, or a ~`MatrixN` if not. """ if self.template is None: raise RuntimeError("Missing template to compute unitary for CompiledCircuit") for f, p in zip(self.parameters, self.template.get_parameters()): p.set_value(f) return self.template.compute_unitary(None, use_symbolic) def describe(self) -> str: """ Describe the component as the Python code that generates it. :return: code generating the component """ if self.version is None: return f"CompiledCircuit({self.name}, {self.m}, {self.parameters})" else: return f"CompiledCircuit({self.name}, {self.m}, {self.parameters}, '{self.version}')" def is_composite(self) -> bool: return self.template is not None and self.template.is_composite() ================================================ FILE: perceval/components/component_catalog.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import importlib from abc import ABC, abstractmethod from perceval.utils import Parameter from perceval.components import Processor, Circuit, Experiment from perceval.utils.logging import get_logger, channel class CatalogItem(ABC): article_ref = None description = None str_repr = None see_also = None params_doc = {} def __init__(self, name: str): self._name = name self._default_backend = 'SLOS' @property def name(self) -> str: """Name of the component :return: Name of the component """ return self._name @property def doc(self): content = '' if self.description: content += f'\n{self.description}\n' if self.article_ref: content += f'\nScientific article reference: {self.article_ref}\n' if self.str_repr: content += f'\nSchema:\n{self.str_repr}\n' if self.params_doc: content += '\nParameters:\n' for param_name, param_descr in self.params_doc.items(): content += f' * {param_name}: {param_descr}\n' if self.see_also: content += f'\nSee also: {self.see_also}\n' if content == '': content = 'None' title = f'{self._name} documentation\n'.upper() title += '-' * len(title) + '\n' return title + content @staticmethod def _handle_param(value): if isinstance(value, str): return Parameter(value) return value @abstractmethod def build_circuit(self, **kwargs) -> Circuit: """Build the component as circuit :return: A Perceval circuit """ pass @abstractmethod def build_experiment(self, **kwargs) -> Experiment: """Build the component as experiment :return: A Perceval experiment """ pass def build_processor(self, **kwargs) -> Processor: """Build the component as processor kwargs: * backend(ABackend or str): Name or instance of a simulation backend. Default "SLOS" :return: A Perceval processor """ return Processor(kwargs.get("backend", self._default_backend), self.build_experiment(**kwargs), name=kwargs.get("name") or self._name.upper()) class Catalog: def __init__(self, path: str): self._items = {} if path: self.add_path(path) def add_path(self, path): module = importlib.import_module(path) if 'catalog_items' in dir(module): sub_catalog = getattr(module, 'catalog_items') self._add_sub_catalog(sub_catalog) else: get_logger().warn(f"No sub catalog found at path {path}", channel.user) def _add_sub_catalog(self, catalog): for cls in catalog: obj = cls() if isinstance(obj, CatalogItem): self._items[obj.name] = obj def list(self) -> list[str]: return list(self._items.keys()) def __contains__(self, item: str) -> bool: return item in self._items def __getitem__(self, item_name: str) -> CatalogItem: return self._items[item_name] ================================================ FILE: perceval/components/core_catalog/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .klm_cnot import KLMCnotItem from .postprocessed_cnot import PostProcessedCnotItem from .heralded_cnot import HeraldedCnotItem from .heralded_cz import HeraldedCzItem from .generic_2mode import Generic2ModeItem from .mzi import MZIPhaseFirst, MZIPhaseLast, SymmetricMZI from .postprocessed_ccz import PostProcessedCCZItem from .postprocessed_cz import PostProcessedCzItem from .qloq_ansatz import QLOQAnsatz from .toffoli import ToffoliItem from .controlled_rotation_gates import PostProcessedControlledRotationsItem from .gates_1qubit import (PauliXItem, PauliYItem, PauliZItem, HadamardItem, RxItem, RyItem, RzItem, PhaseShiftItem, SGateItem, SDagGateItem, TGateItem, TDagGateItem) catalog_items = [ # 2 qubits gate KLMCnotItem, HeraldedCnotItem, PostProcessedCnotItem, HeraldedCzItem, PostProcessedCzItem, # MZIs Generic2ModeItem, MZIPhaseFirst, MZIPhaseLast, SymmetricMZI, # 3 qubits gate PostProcessedCCZItem, ToffoliItem, # N qubits gate PostProcessedControlledRotationsItem, # 1 qubit gate PauliXItem, PauliYItem, PauliZItem, HadamardItem, RxItem, RyItem, RzItem, PhaseShiftItem, SGateItem, SDagGateItem, TGateItem, TDagGateItem, # User defined gates QLOQAnsatz ] ================================================ FILE: perceval/components/core_catalog/_helpers/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .rotations_qloq import apply_rotations_to_qubits from .entanglement_qloq import generalized_cz, generate_chained_controlled_ops ================================================ FILE: perceval/components/core_catalog/_helpers/entanglement_qloq.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components import Circuit, Processor, PERM from perceval.components.core_catalog.postprocessed_cz import PostProcessedCzItem from .rotations_qloq import G_RHk def generalized_cz(n: int, m: int) -> Processor: """ Generates a generalized CZ gate for `n` control and `m` target qubits. Args: n: Number of control qubits. m: Number of target qubits. Returns: Processor: Generalized CZ gate as a Perceval processor object. """ n_modes = 2 ** n m_modes = 2 ** m total_modes = n_modes + m_modes # Identify controls, targets, and swap positions control1, control2 = n_modes - 2, n_modes - 1 target1, target2 = control1 + m_modes, control2 + m_modes mapping = {control1: 0, control2: 1, target1: 2, target2: 3} circ = Processor("SLOS", total_modes, name="GeneralizedCZ") cz = PostProcessedCzItem().build_processor() cz.clear_postselection() cz.remove_port(0) cz.remove_port(2) circ.add(mapping, cz) return circ def generate_permutation_for_controlled_op(control: int, target: int, num_qubits: int) -> list[int]: """ Generate the permutation required for a controlled NOT operation :param control: Control qubit :param target: Target qubit :param num_qubits: Number of qubits in this qudit operation. :return: the permutation list. """ m = 2 ** num_qubits perm = list(range(m)) for i in range(m): binary = format(i, f'0{num_qubits}b') if binary[control] == '1': flipped = list(binary) flipped[target] = '0' if flipped[target] == '1' else '1' perm[i] = int("".join(flipped), 2) return perm def create_internal_controlled_op(op_type: str, control: int, target: int, num_qubits: int) -> Circuit: """ Generate a CNOT or CZ gate using the given `control` qubit and `target` qubit for a qudit of size `num_qubits`. :param op_type: Operation type (either CNOT or CZ) :param control: Control qubit :param target: Target qubit :param num_qubits: Number of qubits in this qudit operation. :return: CNOT or CZ gate, internal to the qudit. """ circ = Circuit(2**num_qubits, name=f"{op_type}") # toggling G_RHK makes the below CX into a CZ. if op_type == "CZ": circ.add(0, G_RHk(num_qubits, target)) perm = generate_permutation_for_controlled_op(control, target, num_qubits) circ.add(0, PERM(perm)) # This is a CX in qudit encoding if op_type == "CZ": circ.add(0, G_RHk(num_qubits, target)) return circ def generate_chained_controlled_ops(op_type: str, n: int) -> Circuit: """ Generates a circuit with a chain of controlled operations. Args: op_type: Type of controlled operation ("CZ" or "CX"). n: Number of qubits in the circuit. Returns: Circuit: A circuit containing the chained controlled operations. """ circ = Circuit(2**n, name=f"{op_type}{n}") for i in range(n-1): circ.add(0, create_internal_controlled_op(op_type, i, i + 1, n)) return circ ================================================ FILE: perceval/components/core_catalog/_helpers/rotations_qloq.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components.core_catalog.gates_1qubit import HadamardItem, RxItem, RyItem, RzItem from perceval.utils import Parameter from perceval.components import Circuit, PERM def internal_swap(qubit1: int, qubit2: int, num_qubits: int) -> Circuit: """ Creates an internal SWAP gate that swaps two qubits within a quantum circuit in a single qudit encoding. Args: qubit1: The index of the first qubit to be swapped. qubit2: The index of the second qubit to be swapped. num_qubits: The total number of qubits in the circuit. Returns: A Perceval circuit object representing the SWAP operation. Raises: ValueError: If either of the qubit indices are invalid or if they are the same. >>> internal_swap(1, 3, 4) will swap the second and fourth qubits in a 4-qubit system. Note: The function constructs a permutation that describes the SWAP operation for the given qubits """ # Check if qubit1 and qubit2 are valid qubit indices if qubit1 >= num_qubits or qubit2 >= num_qubits or qubit1 == qubit2: raise ValueError("Invalid qubit indices for SWAP.") # Calculate the number of possible states num_states = 2 ** num_qubits # Create a list of all possible states states = [bin(i)[2:].zfill(num_qubits) for i in range(num_states)] # Generate the swapped states list swapped_states = [] for state in states: state_list = list(state) # Swap the bits of the two qubits state_list[qubit1], state_list[qubit2] = state_list[qubit2], state_list[qubit1] swapped_states.append(''.join(state_list)) # Converts back to str # Map the states to their indices to get the permutation permutation = [states.index(swap_state) for swap_state in swapped_states] # Construct the circuit circ = Circuit(num_states, name=f"SWAP{qubit1}{qubit2}") circ.add(tuple(range(num_states)), PERM(permutation)) return circ def _generate_rotation_kth_qubit(gate_layer: Circuit, nqubits: int, k: int, circuit_name: str) -> Circuit: """Apply the given gate to the k-th qubit of n qubits for a circuit in a single qudit encoding.""" if k == nqubits - 1: return gate_layer circ = Circuit(2 ** nqubits, name=circuit_name) # Add the internal swap gate to swap the k-th qubit to the (n-1)-th qubit. circ.add(0, internal_swap(k, nqubits - 1, nqubits), merge=True) # Apply the rotation gate on the (nqubits-1)-th qubit. circ.add(0, gate_layer, merge=True) # Revert the modes to their original positions. circ.add(0, internal_swap(k, nqubits - 1, nqubits), merge=True) return circ def _generate_rotation_last_qubit(gate: Circuit, nqubits: int) -> Circuit: """Apply the gate to nth qubit for a circuit in a single qudit encoding.""" circ = Circuit(2 ** nqubits, name=gate.name) for i in range(0, 2 ** nqubits, 2): circ.add(i, gate) return circ def _g_rn(gate_name: str, angle: float | Parameter, n: int ) -> Circuit: gates = {"X": RxItem, "Y": RyItem, "Z": RzItem} return _generate_rotation_last_qubit(gates[gate_name]().build_circuit(theta=angle), nqubits=n) def G_RHn(n: int) -> Circuit: """Apply the Hadamard gate to nth qubit for a circuit in a single qudit encoding.""" return _generate_rotation_last_qubit(HadamardItem().build_circuit(), nqubits=n) def G_RHk(n: int, k: int) -> Circuit: """Apply the Hadamard gate to the k-th qubit of n qubits for a circuit in a single qudit encoding.""" return _generate_rotation_kth_qubit(G_RHn(n), n, k, f"RH{k}") def _g_rk(angle: float, n: int, k: int, rotation: str) -> Circuit: return _generate_rotation_kth_qubit(_g_rn(rotation, angle, n), n, k, f"R{rotation}{k}") def apply_rotations_to_qubits(angle_list: list[float | Parameter], n: int, rotation: str): """Apply the rotation gate to each qubit in a group of n qubits based on an angle list.""" assert len(angle_list) == n, "Angle list should match the number of qubits in the group." assert rotation in ["Y", "Z", "X"], "Rotation must be X or Y or Z." circ = Circuit(2 ** n, name=f"R{rotation}QUDIT{n}") # Apply the rotation gate for each qubit based on the provided angle. for idx, angle in enumerate(angle_list): circ.add(0, _g_rk(angle, n, idx, rotation), merge=True) return circ ================================================ FILE: perceval/components/core_catalog/controlled_rotation_gates.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import numpy as np import cmath as cm from scipy.linalg import block_diag from perceval.components import Circuit, Port, Unitary, Processor, Experiment from perceval.components.component_catalog import CatalogItem from perceval.utils import Encoding, PostSelect, Matrix def build_control_gate_unitary(n: int, alpha: float) -> Matrix: """ Builds the Unitary transformation of the n-qubit controlled-rotation gate C...CZ(alpha) :param n: number of qubits of the gate :param alpha: rotation angle of the gate :return: Unitary matrix of the post-selected gate Ref : https://arxiv.org/abs/2405.01395 """ I = Matrix.eye(n) a = (cm.exp(alpha * 1j) - 1) ** (1 / n) J = np.roll(Matrix.eye(n), shift=1, axis=1) # Main matrix block A0 = I + a * J B0 = Matrix.eye(n) M = block_diag(B0, A0) U0 = Matrix.get_unitary_extension(M) # Setting the modes right for the dual rail encoding initial_modes = [i for i in range(4*n)] final_modes = [ 2*i for i in range(n)] + [2*i+1 for i in range(n)] + [i for i in range(2*n, 4*n)] perm = Matrix.zeros((4*n, 4*n)) perm[initial_modes, final_modes] = 1 U = perm.T @ U0 @ perm return U class PostProcessedControlledRotationsItem(CatalogItem): article_ref = "https://arxiv.org/abs/2405.01395" description = r"""n-qubit controlled rotation gate C...CZ(alpha) with 2*n ancillary modes and a post-selection function""" params_doc = { "n": "number of qubit of the gate", "alpha": "angle of the controlled-rotation" } str_repr = r""" ╭─────╮ ctrl0 (dual rail) ─────┤ ├───── ctrl0 (dual rail) ─────┤ ├───── │ │ ctrl1 (dual rail) ─────┤ ├───── ctrl1 (dual rail) ─────┤ ├───── . . . . . . . . . ctrlN (dual rail) ─────┤ ├───── ctrlN (dual rail) ─────┤ ├───── ╰─────╯""" def __init__(self): super().__init__("postprocessed controlled gate") def build_circuit(self, **kwargs) -> Circuit: """ kwargs: - n : int, number of qubit of the gate. - alpha : float, angle of the controlled-rotation. :return: Circuit implementing the post-selected n-qubit controlled gate. """ if "n" not in kwargs: raise KeyError("Missing input n") n = kwargs["n"] if not isinstance(n, int): raise TypeError("n must be of type int.") if n < 2: raise ValueError(f"n must be at least 2. Here n = {n}.") alpha = kwargs.get("alpha", math.pi) if not isinstance(alpha, float): raise TypeError("alpha must be of type float.") m = build_control_gate_unitary(n, alpha) return Circuit(4*n, name="postprocessed controlled gate").add(0, Unitary(m)) def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) n = kwargs["n"] e.set_postselection(PostSelect('&'.join([f"[{2*n},{2*n+1}]==1" for n in range(n)]))) for i in range(n - 1): e.add_port(2 * i, Port(Encoding.DUAL_RAIL, f"ctrl{i}")) e.add_port(2 * (n - 1), Port(Encoding.DUAL_RAIL, "data")) for i in range(2 * n, 4 * n): e.add_herald(i, 0) return e ================================================ FILE: perceval/components/core_catalog/gates_1qubit.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math from abc import ABC, abstractmethod from numbers import Number from perceval.components import Processor, Circuit, BS, PS, PERM, Port, Experiment from perceval.utils import Encoding, P from perceval.components.component_catalog import CatalogItem _COMPONENT_STRING_REPR = [ (" ╭", "╮"), ("Q (dual rail) ──┤", "├── Q (dual rail)"), (" ──┤", "├──"), (" ╰", "╯")] def _get_component_str_repr(repr_name: str) -> str: str_len = len(repr_name) + 2 output_str = "" for i in range(len(_COMPONENT_STRING_REPR)): output_str += _COMPONENT_STRING_REPR[i][0] if i == 1: output_str += f" {repr_name} " elif i == 2: output_str += ' ' * str_len else: output_str += '─' * str_len output_str += _COMPONENT_STRING_REPR[i][1] + '\n' return output_str class ASingleQubitGate(CatalogItem, ABC): repr_name: str catalog_name: str doc_name: str def __init__(self): super().__init__(self.catalog_name) @property def description(self): return f"2 modes LO circuit equivalent of the {self.doc_name} single Qubit Gate." @property def str_repr(self): return _get_component_str_repr(self.repr_name) def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) return e.add_port(0, Port(Encoding.DUAL_RAIL, 'data')) class AFixedItem(ASingleQubitGate, ABC): circuit: Circuit def build_circuit(self, **kwargs) -> Circuit: return Circuit(2, name=self.repr_name) // self.circuit class HadamardItem(AFixedItem): repr_name = "H" catalog_name = "h" doc_name = "Hadamard" circuit = BS.H() class SGateItem(AFixedItem): repr_name = "S" catalog_name = "s" doc_name = repr_name circuit = (1, PS(math.pi / 2)) class SDagGateItem(AFixedItem): repr_name = "S†" catalog_name = "sdag" doc_name = ":math:`S^†`" circuit = (1, PS(-math.pi / 2)) class TGateItem(AFixedItem): repr_name = "T" catalog_name = "t" doc_name = repr_name circuit = (1, PS(math.pi / 4)) class TDagGateItem(AFixedItem): repr_name = "T†" catalog_name = "tdag" doc_name = ":math:`T^†`" circuit = (1, PS(-math.pi / 4)) class APauliItem(AFixedItem, ABC): axis: str @property def repr_name(self): return self.axis @property def catalog_name(self): return self.axis.lower() @property def doc_name(self): return f"Pauli {self.axis}" class PauliXItem(APauliItem): axis = "X" circuit = PERM([1, 0]) class PauliYItem(APauliItem): axis = "Y" circuit = PERM([1, 0]) // (1, PS(math.pi / 2)) // (0, PS(-math.pi / 2)) class PauliZItem(APauliItem): axis = "Z" circuit = (1, PS(math.pi)) class AParamItem(ASingleQubitGate, ABC): param_key: str @abstractmethod def get_circuit(self, param) -> Circuit: pass def build_circuit(self, **kwargs) -> Circuit: param = kwargs.get(self.param_key, 0.0) param = self._handle_param(param) name = self.repr_name if isinstance(param, int): name = f"{self.repr_name}({param})" elif isinstance(param, Number): name = f"{self.repr_name}({param:.3})" elif isinstance(param, P): name = f"{self.repr_name}({param.name})" return Circuit(2, name) // self.get_circuit(param) class PhaseShiftItem(AParamItem): repr_name = "Ps" catalog_name = "ph" doc_name = "Phase Shifter" param_key = "phi" def get_circuit(self, phi): return Circuit(2)//(1, PS(phi)) class ARItem(AParamItem, ABC): axis: str param_key = "theta" @property def repr_name(self): return f"R{self.axis.lower()}" @property def catalog_name(self): return self.repr_name.lower() @property def doc_name(self): return self.repr_name class RxItem(ARItem): axis = "X" def get_circuit(self, theta): return BS.Rx(theta=-theta) class RyItem(ARItem): axis = "Y" def get_circuit(self, theta): return BS.Ry(theta=theta) class RzItem(ARItem): axis = "Z" def get_circuit(self, theta): return Circuit(2) // (0, PS(-theta / 2)) // (1, PS(theta / 2)) ================================================ FILE: perceval/components/core_catalog/generic_2mode.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components import Circuit, BS, Processor, Experiment from perceval.components.component_catalog import CatalogItem class Generic2ModeItem(CatalogItem): description = "A universal 2 mode component, implemented as a beam splitter with variable theta + 3 free phases" str_repr = r""" ╭──────╮╭─────╮╭──────╮ 0:──┤phi_tl├┤ ├┤phi_tr├──:0 ╰──────╯│BS.H │╰──────╯ ╭──────╮│theta│ 1:──┤phi_bl├┤ ├──────────:1 ╰──────╯╰─────╯ """ params_doc = { "theta": "name or numerical value for beam splitter 'theta' parameter (default 'theta')", "phi_tl": "name or numerical value for top left phase (default 'phi_tl')", "phi_bl": "name or numerical value for bottom left phase (default 'phi_bl')", "phi_tr": "name or numerical value for top right phase (default 'phi_tr')" } def __init__(self): super().__init__("generic 2 mode circuit") def build_circuit(self, **kwargs) -> Circuit: return Circuit(2, name=kwargs.get("name", "U2")) \ // BS.H(theta=self._handle_param(kwargs.get("theta", "theta")), phi_tl=self._handle_param(kwargs.get("phi_tl", "phi_tl")), phi_bl=self._handle_param(kwargs.get("phi_bl", "phi_bl")), phi_tr=self._handle_param(kwargs.get("phi_tr", "phi_tr"))) def build_experiment(self, **kwargs) -> Experiment: return Experiment(self.build_circuit(**kwargs)) ================================================ FILE: perceval/components/core_catalog/heralded_cnot.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components import Circuit, BS, Port from perceval.components.component_catalog import CatalogItem from perceval.components.core_catalog.heralded_cz import HeraldedCzItem from perceval.components.experiment import Experiment from perceval.components.processor import Processor from perceval.utils import Encoding class HeraldedCnotItem(CatalogItem): article_ref = "" description = r"""Knill CNOT gate with 2 heralded modes (built using Heralded CZ and H).""" str_repr = r""" ╭──────────╮ ctrl (dual rail) ─────────────┤ ├───────────── ctrl (dual rail) ─────────────┤ Heralded ├───────────── ╭───╮ │ CZ │ ╭───╮ data (dual rail) ─────┤ H ├───┤ ├───┤ H ├───── data (dual rail) ─────┤ ├───┤ ├───┤ ├───── ╰───╯ ╰──────────╯ ╰───╯""" see_also = "heralded cz, postprocessed cnot and klm cnot" def __init__(self): super().__init__("heralded cnot") def build_circuit(self, **kwargs) -> Circuit: c = Circuit(6, name="Heralded CNOT") c.add(2, BS.H()) heralded_cz = HeraldedCzItem() c.add(0, heralded_cz.build_circuit(), merge=True) c.add(2, BS.H()) return c def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) return e.add_port(0, Port(Encoding.DUAL_RAIL, 'ctrl'))\ .add_port(2, Port(Encoding.DUAL_RAIL, 'data'))\ .add_herald(4, 1)\ .add_herald(5, 1) ================================================ FILE: perceval/components/core_catalog/heralded_cz.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math from perceval.components import Processor, Circuit, PERM, BS, PS, Barrier, Experiment from perceval.components.component_catalog import CatalogItem from perceval.components.port import Port, Encoding class HeraldedCzItem(CatalogItem): article_ref = "https://arxiv.org/abs/quant-ph/0110144" description = r"""Knill CZ gate with 2 heralded modes""" str_repr = r""" ╭─────╮ ctrl (dual rail) ─────┤ ├───── ctrl (dual rail) ─────┤ ├───── │ │ data (dual rail) ─────┤ ├───── data (dual rail) ─────┤ ├───── ╰─────╯""" theta1 = math.acos(math.sqrt(1/3))*2 theta2 = math.acos(math.sqrt((3+math.sqrt(6))/6))*2 def __init__(self): super().__init__("heralded cz") def build_circuit(self, **kwargs) -> Circuit: # the matrix of this first circuit is the same as the one presented in the reference paper, the difference in the second phase shift - placed on mode 3 instead of mode 1 - is due to a different convention for the beam-splitters (signs inverted in second column). last_modes_cz = (Circuit(4) .add((1, 2), PERM([1, 0])) .add(0, Barrier(4, visible=False)) # Align components .add(0, PS(math.pi)) .add(3, PS(math.pi)) .add(0, Barrier(4, visible=False)) # Align components .add((0, 1), BS.H(theta=self.theta1)) .add((2, 3), BS.H(theta=self.theta1)) .add(0, Barrier(4, visible=False)) # Align components .add((1, 2), PERM([1, 0])) .add((0, 1), BS.H(theta=-self.theta1)) .add((2, 3), BS.H(theta=self.theta2))) return (Circuit(6, name="Heralded CZ") .add(1, PERM([1, 0])) .add(2, last_modes_cz, merge=True) .add(1, PERM([1, 0]))) def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) return e.add_port(0, Port(Encoding.DUAL_RAIL, 'ctrl')) \ .add_port(2, Port(Encoding.DUAL_RAIL, 'data')) \ .add_herald(4, 1) \ .add_herald(5, 1) ================================================ FILE: perceval/components/core_catalog/klm_cnot.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from math import sqrt from perceval.components import Circuit, BS, PERM, Port, Processor, Experiment from perceval.components.component_catalog import CatalogItem from perceval.utils import Encoding _GATE_NAME = "KLM CNOT" class KLMCnotItem(CatalogItem): article_ref = "https://doi.org/10.1073/pnas.1018839108" description = f"{_GATE_NAME} gate with 4 ancillary modes.\n" \ "You probably shouldn't use the KLM CNOT, except for educational purpose. The Knill CNOT is " \ "better in every aspect (see 'heralded cnot' in the catalog)." str_repr = r""" ╭─────╮ ctrl (dual rail) ─────┤ ├───── ctrl (dual rail) ─────┤ ├───── │ │ data (dual rail) ─────┤ ├───── data (dual rail) ─────┤ ├───── ╰─────╯""" see_also = "postprocessed cnot and heralded cnot (using cz)" R1 = (3 - sqrt(2)) / 7 R2 = 5 - 3 * sqrt(2) theta1 = BS.r_to_theta(R1) theta2 = BS.r_to_theta(R2) def __init__(self): super().__init__("klm cnot") def build_circuit(self, **kwargs) -> Circuit: return (Circuit(8, name=_GATE_NAME) .add(1, PERM([2, 4, 3, 0, 1])) .add(4, BS.H()) .add(3, PERM([1, 3, 0, 4, 2])) .add(3, BS.H()) .add(3, PERM([2, 0, 1])) .add(2, BS.H(theta=self.theta1)) .add(4, BS.H(theta=self.theta1)) .add(3, PERM([1, 2, 0])) .add(3, BS.H()) .add(1, PERM([2, 0, 3, 1, 6, 5, 4])) .add(2, BS.H(theta=self.theta2)) .add(2, PERM([1, 0])) .add(4, BS.H(theta=self.theta2)) .add(4, PERM([1, 2, 0])) .add(4, BS.H()) .add(1, PERM([4, 3, 0, 2, 1]))) def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) return e.add_port(0, Port(Encoding.DUAL_RAIL, 'ctrl')) \ .add_port(2, Port(Encoding.DUAL_RAIL, 'data')) \ .add_herald(4, 0) \ .add_herald(5, 1) \ .add_herald(6, 0) \ .add_herald(7, 1) ================================================ FILE: perceval/components/core_catalog/mzi.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math from abc import ABC from perceval.components import Processor, Circuit, BS, PS, Experiment from perceval.components.component_catalog import CatalogItem class AMZI(CatalogItem, ABC): description = "Mach-Zehnder interferometer" params_doc = { 'phi_a': "first phase of the MZI (default 'phi_a')", 'phi_b': "second phase of the MZI (default 'phi_b')", 'theta_a': "theta value of the first beam splitter (default pi/2)", 'theta_b': "theta value of the second beam splitter (default pi/2)", } @staticmethod def _handle_params(**kwargs): if "i" in kwargs: kwargs["phi_a"] = f"phi_a{kwargs['i']}" kwargs["phi_b"] = f"phi_b{kwargs['i']}" return CatalogItem._handle_param(kwargs.get("phi_a", "phi_a")), \ CatalogItem._handle_param(kwargs.get("phi_b", "phi_b")), \ CatalogItem._handle_param(kwargs.get("theta_a", math.pi/2)), \ CatalogItem._handle_param(kwargs.get("theta_b", math.pi/2)) def build_experiment(self, **kwargs) -> Experiment: return Experiment(self.build_circuit(**kwargs)) def generate(self, i: int): return self.build_circuit(i=i) _NAME_MZI_PHASE_FIRST = "mzi phase first" _NAME_MZI_PHASE_LAST = "mzi phase last" _NAME_SYMMETRIC_MZI = "symmetric mzi" class MZIPhaseFirst(AMZI): str_repr = r""" ╭─────╮╭─────╮╭─────╮╭─────╮ 0:──┤phi_a├┤BS.Rx├┤phi_b├┤BS.Rx├──:0 ╰─────╯│ │╰─────╯│ │ 1:─────────┤ ├───────┤ ├──:1 ╰─────╯ ╰─────╯ """ see_also = _NAME_MZI_PHASE_LAST def __init__(self): super().__init__(_NAME_MZI_PHASE_FIRST) def build_circuit(self, **kwargs) -> Circuit: phi_a, phi_b, theta_a, theta_b = self._handle_params(**kwargs) return (Circuit(2, name="MZI") // (0, PS(phi=phi_a)) // BS(theta=theta_a) // (0, PS(phi=phi_b)) // BS(theta=theta_b)) class MZIPhaseLast(AMZI): str_repr = r""" ╭─────╮ ╭─────╮ 0:──┤BS.Rx├───────┤BS.Rx├─────────:0 │ │╭─────╮│ │╭─────╮ 1:──┤ ├┤phi_a├┤ ├┤phi_b├──:1 ╰─────╯╰─────╯╰─────╯╰─────╯ """ see_also = _NAME_MZI_PHASE_FIRST def __init__(self): super().__init__(_NAME_MZI_PHASE_LAST) def build_circuit(self, **kwargs) -> Circuit: phi_a, phi_b, theta_a, theta_b = self._handle_params(**kwargs) return (Circuit(2, name="MZI") // BS(theta=theta_a) // (1, PS(phi=phi_a)) // BS(theta=theta_b) // (1, PS(phi=phi_b))) class SymmetricMZI(AMZI): str_repr = r""" ╭─────╮╭─────╮╭─────╮ 0:──┤BS.Rx├┤phi_a├┤BS.Rx├──:0 │ │╰─────╯│ │ │ │╭─────╮│ │ 1:──┤ ├┤phi_b├┤ ├──:1 ╰─────╯╰─────╯╰─────╯ """ see_also = _NAME_MZI_PHASE_FIRST def __init__(self): super().__init__(_NAME_SYMMETRIC_MZI) def build_circuit(self, **kwargs) -> Circuit: phi_a, phi_b, theta_a, theta_b = self._handle_params(**kwargs) return (Circuit(2, name="MZI") // BS(theta=theta_a) // (0, PS(phi=phi_a)) // (1, PS(phi=phi_b))) // BS(theta=theta_b) ================================================ FILE: perceval/components/core_catalog/postprocessed_ccz.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from math import pi from perceval.components import Circuit, Port, Unitary, Processor, Experiment from perceval.components.component_catalog import CatalogItem from perceval.components.core_catalog import controlled_rotation_gates from perceval.utils import Encoding, PostSelect, Matrix class PostProcessedCCZItem(CatalogItem): article_ref = "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.65.062324" description = r"""CCZ gate with 6 heralded modes and a post-selection function""" str_repr = r""" ╭─────╮ ctrl0 (dual rail) ─────┤ ├───── ctrl0 (dual rail) ─────┤ ├───── │ │ ctrl1 (dual rail) ─────┤ ├───── ctrl1 (dual rail) ─────┤ ├───── │ │ data (dual rail) ─────┤ ├───── data (dual rail) ─────┤ ├───── ╰─────╯""" def __init__(self): super().__init__("postprocessed ccz") def build_circuit(self, **kwargs) -> Circuit: m = Matrix(controlled_rotation_gates.build_control_gate_unitary(3, pi)) return Circuit(12, name="PostProcessed CCZ").add(0, Unitary(m)) def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) e.set_postselection(PostSelect("[0,1]==1 & [2,3]==1 & [4,5]==1")) return e.add_port(0, Port(Encoding.DUAL_RAIL, 'ctrl0')) \ .add_port(2, Port(Encoding.DUAL_RAIL, 'ctrl1')) \ .add_port(4, Port(Encoding.DUAL_RAIL, 'data')) \ .add_herald(6, 0) \ .add_herald(7, 0) \ .add_herald(8, 0) \ .add_herald(9, 0) \ .add_herald(10, 0) \ .add_herald(11, 0) ================================================ FILE: perceval/components/core_catalog/postprocessed_cnot.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .postprocessed_cz import PostProcessedCzItem from perceval.components import Circuit, BS, Port, Processor, Experiment from perceval.components.component_catalog import CatalogItem from perceval.utils import Encoding, PostSelect class PostProcessedCnotItem(CatalogItem): article_ref = "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.65.062324" description = r"""CNOT gate with 2 heralded modes and a post-selection function""" str_repr = r""" ╭─────╮ ctrl (dual rail) ─────┤ ├───── ctrl (dual rail) ─────┤ ├───── │ │ data (dual rail) ─────┤ ├───── data (dual rail) ─────┤ ├───── ╰─────╯""" see_also = "klm cnot and heralded cnot (using cz)" def __init__(self): super().__init__("postprocessed cnot") def build_circuit(self, **kwargs) -> Circuit: postprocessed_cz = PostProcessedCzItem() return (Circuit(6, name="PostProcessed CNOT") .add((2, 3), BS.H()) .add(0, postprocessed_cz.build_circuit(), merge=True) .add((2, 3), BS.H())) def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) e.set_postselection(PostSelect("[0,1]==1 & [2,3]==1")) return e.add_port(0, Port(Encoding.DUAL_RAIL, 'ctrl')) \ .add_port(2, Port(Encoding.DUAL_RAIL, 'data')) \ .add_herald(4, 0) \ .add_herald(5, 0) ================================================ FILE: perceval/components/core_catalog/postprocessed_cz.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components import Circuit, PERM, BS, Port, Barrier, Experiment from perceval.components.component_catalog import CatalogItem from perceval.utils import Encoding, PostSelect class PostProcessedCzItem(CatalogItem): article_ref = "https://journals.aps.org/pra/abstract/10.1103/PhysRevA.65.062324" description = r"""CZ gate with 2 heralded modes and a post-selection function""" str_repr = r""" ╭─────╮ ctrl (dual rail) ─────┤ ├───── ctrl (dual rail) ─────┤ ├───── │ │ data (dual rail) ─────┤ ├───── data (dual rail) ─────┤ ├───── ╰─────╯""" see_also = "heralded cz" def __init__(self): super().__init__("postprocessed cz") def build_circuit(self, **kwargs) -> Circuit: theta_13 = BS.r_to_theta(1 / 3) return (Circuit(6, name="PostProcessed CZ") .add(1, PERM([2, 1, 3, 0])) # So that both heralded modes are on the bottom of the gate .add(0, Barrier(6, visible=False)) .add((0, 1), BS.H(theta_13)) .add((2, 3), BS.H(theta_13)) .add((4, 5), BS.H(theta_13)) .add(1, PERM([3, 1, 0, 2]))) # So that both heralded modes are on the bottom of the gate def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) e.set_postselection(PostSelect("[0,1]==1 & [2,3]==1")) return e.add_port(0, Port(Encoding.DUAL_RAIL, 'ctrl')) \ .add_port(2, Port(Encoding.DUAL_RAIL, 'data')) \ .add_herald(4, 0) \ .add_herald(5, 0) ================================================ FILE: perceval/components/core_catalog/qloq_ansatz.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components.component_catalog import CatalogItem from ._helpers import generate_chained_controlled_ops, generalized_cz, apply_rotations_to_qubits from perceval.components import Circuit, Processor, Port, Experiment from perceval.utils import Encoding, PostSelect class QLOQAnsatz(CatalogItem): description = "A QLOQ Ansatz generator, linking qudits with CZ gates" str_repr = "User defined (depends on the parameters)" params_doc = { "group_sizes": "list of DUAL_RAIL or QUDITn Encodings", "layers": "list of rotation layers to apply. Values can be either 'X', 'Y', or 'Z'", "phases": "list of names or numerical values for qudit phases (default 'phi{i}'). " "Required length can be computed using self.get_parameter_nb", "ctype": "name of the entanglement gate to apply. Value can be either 'cx' or 'cz' (default 'cx')", } article_ref = "https://arxiv.org/pdf/2411.03878" def __init__(self): super().__init__('qloq ansatz') self._circ = None self._lp = None self._angle_offset = 0 self._layers = None def _apply_layer_operations(self, offset: int, size: int) -> None: """ Applies a set of layer operations to a segment of the quantum circuit. Args: offset: The starting mode on which to apply the layer operations. size: The size of the qubit group for which to apply layer operations. """ for layer in self._layers: self._circ.add(offset, apply_rotations_to_qubits(self._lp[self._angle_offset:self._angle_offset + size], size, layer)) self._angle_offset += size def _add_single_layer(self, offset: int, size: int, ctype: str): """ - Use the apply_layer_operations() function to set rotational layers based on the angles. - Add entanglement with generate_chained_controlled_ops. """ self._apply_layer_operations(offset, size) self._circ.add(offset, generate_chained_controlled_ops(ctype, size)) self._apply_layer_operations(offset, size) def _build_qubit_circuit(self, qubit_group_sizes: list[int], lp: list[float], layers: list[str], ctype="cx") -> Processor: """ Builds a quantum circuit based on specified parameters. The circuit is generated for multiple groups of qubits with custom operations and entanglement. Args: qubit_group_sizes (list of int): List of sizes for each group of qubits. lp (list of float): List of angles for the parameterized gates. layers (list of str): Types of rotation layers to apply ('X', 'Y', 'Z'). ctype (str, optional): The type of controlled operation to use ("cz" or "cx"). Defaults to "cx". Returns: Processor: The constructed quantum circuit as a Processor. """ ctype = ctype.upper() total_modes = sum((2 ** n for n in qubit_group_sizes)) self._circ = Processor("SLOS", total_modes, name="Machine Learning") self._layers = layers self._angle_offset = 0 self._lp = lp offset = 0 previous_size = None for j, size in enumerate(qubit_group_sizes): self._add_single_layer(offset, size, ctype) if previous_size is not None: # Add a Generalized Controlled-Z (CZ) gate between the current and previous group. self._circ.add(offset - 2 ** previous_size, generalized_cz(previous_size, size)) if j == len(qubit_group_sizes) - 1: break self._add_single_layer(offset, size, ctype) # Update the offset for the next iteration offset += 2 ** size previous_size = size # Reset offset and apply the final set of operations for all groups offset = 0 for size in qubit_group_sizes: self._add_single_layer(offset, size, ctype) offset += 2 ** size return self._circ @staticmethod def get_parameter_nb(qubit_group_sizes: list[Encoding], nb_layers: int) -> int: """ Calculate the total number of parameters needed for a quantum circuit with the given qubit group sizes and number of layers. Args: qubit_group_sizes: A list containing the encoding of each qubit group in the circuit. nb_layers: the number of layers (e.g., 'X', 'Y', 'Z') in the circuit. Returns: int: The total number of parameters required for the circuit. """ qubit_group_sizes = [size.logical_length for size in qubit_group_sizes] # Each single layer appears twice for the first and last groups, and three times for the others depths = [2 if (i in (0, len(qubit_group_sizes) - 1)) else 3 for i in range(len(qubit_group_sizes))] # Calculate parameters per depth for each group parameters = [depth * size * 2 * nb_layers for depth, size in zip(depths, qubit_group_sizes)] return sum(parameters) def build_circuit(self, **kwargs) -> Circuit: assert "group_sizes" in kwargs, "missing required argument: 'group_sizes'" assert "layers" in kwargs, "missing required argument: 'layers'" group_sizes = kwargs["group_sizes"] assert len(group_sizes), "group_sizes is empty" for size in group_sizes: assert isinstance(size, Encoding), f"size must be a logical Encoding, got {type(size).__name__}" assert size == Encoding.DUAL_RAIL or size.name.startswith("QUDIT"), "Incompatible encoding for {size}" layers = kwargs["layers"] assert len(layers), "No layers provided" assert all(l in ("X", "Y", "Z") for l in layers), "layers can only be 'X', 'Y', 'Z'" ctype = kwargs.get("ctype", "cx") assert ctype in ["cx", "cz"], "ctype must be either 'cx' or 'cz'" phases = kwargs.get("phases", None) parameter_nb = self.get_parameter_nb(group_sizes, len(layers)) if phases is not None: assert isinstance(phases, list), "phases must be a list" assert len(phases) == parameter_nb, \ f"there must be enough phases for the circuit {parameter_nb} (got {len(phases)})" else: phases = self._generate_phases(parameter_nb) phases = [self._handle_param(phase) for phase in phases] group_sizes = [size.logical_length for size in group_sizes] self._build_qubit_circuit(group_sizes, phases, layers, ctype) return self._circ.linear_circuit() def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) group_sizes = kwargs["group_sizes"] offset = 0 post_select_str = "" for i, size in enumerate(group_sizes): m = size.fock_length e.add_port(offset, Port(size, f"Group {i}")) post_select_str += f" & {list(range(offset, offset + m))} == 1" offset += m e.set_postselection(PostSelect(post_select_str[3:])) nb_heralds = 2 * (len(group_sizes) - 1) for _ in range(nb_heralds): e.add_herald(e.m - 1, 0) return e @staticmethod def _generate_phases(parameter_nb: int) -> list[str]: return [f"phi{i}" for i in range(parameter_nb)] ================================================ FILE: perceval/components/core_catalog/toffoli.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components import Circuit, Port, BS, Processor, Experiment from perceval.components.component_catalog import CatalogItem from perceval.components.core_catalog.postprocessed_ccz import PostProcessedCCZItem from perceval.utils import Encoding, PostSelect class ToffoliItem(CatalogItem): description = r"""Toffoli gate CCNOT gate with 6 heralded modes and a post-selection function (built using post-processed CCZ and H).""" str_repr = r""" ╭──────────╮ ctrl0 (dual rail) ─────────────┤ ├───────────── ctrl0 (dual rail) ─────────────┤ ├───────────── ctrl1 (dual rail) ─────────────┤ ├───────────── ctrl1 (dual rail) ─────────────┤ CCZ ├───────────── ╭───╮ │ │ ╭───╮ data (dual rail) ─────┤ H ├───┤ ├───┤ H ├───── data (dual rail) ─────┤ ├───┤ ├───┤ ├───── ╰───╯ ╰──────────╯ ╰───╯""" def __init__(self): super().__init__("toffoli") def build_circuit(self, **kwargs) -> Circuit: c = Circuit(12, name="Toffoli") c.add(4, BS.H()) c.add(0, PostProcessedCCZItem().build_circuit(), merge=True) c.add(4, BS.H()) return c def build_experiment(self, **kwargs) -> Experiment: e = Experiment(self.build_circuit(**kwargs)) e.set_postselection(PostSelect("[0,1]==1 & [2,3]==1 & [4,5]==1")) return e.add_port(0, Port(Encoding.DUAL_RAIL, 'ctrl0')) \ .add_port(2, Port(Encoding.DUAL_RAIL, 'ctrl1')) \ .add_port(4, Port(Encoding.DUAL_RAIL, 'data')) \ .add_herald(6, 0) \ .add_herald(7, 0) \ .add_herald(8, 0) \ .add_herald(9, 0) \ .add_herald(10, 0) \ .add_herald(11, 0) ================================================ FILE: perceval/components/detector.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing import copy from abc import ABC, abstractmethod from enum import Enum from functools import cache from math import comb from .abstract_component import AComponent from .linear_circuit import Circuit from .unitary_components import BS, PERM from perceval.utils import FockState, BSDistribution from perceval.utils.logging import channel, get_logger class DetectionType(Enum): """Type of photon detection. """ PNR = 0 Threshold = 1 PPNR = 2 Mixed = 3 DetectionType.PNR.__doc__ = "Photon Number Resolving (perfect detection)" DetectionType.Threshold.__doc__ = "Threshold detection (detects 1 photon at most)" DetectionType.PPNR.__doc__ = "Pseudo PNR" DetectionType.Mixed.__doc__ = "Multiple DetectionType" class IDetector(AComponent, ABC): def __init__(self): super().__init__(1) @property @abstractmethod def type(self) -> DetectionType: """ Returns the detector type. """ @abstractmethod def detect(self, theoretical_photons: int) -> BSDistribution | FockState: """ Returns a one mode Fock state or distribution out of a theoretical photon count hitting the detector. :param theoretical_photons: Number of photons hitting the detector simultaneously. :return: The resulting measured state or distribution of all possible measurements. """ def copy(self) -> IDetector: return copy.copy(self) @property @abstractmethod def max_detections(self): pass @property @abstractmethod def efficiency(self): pass class BSLayeredPPNR(IDetector): r""" BSLayeredPPNR implements Pseudo Photon Number Resolving detection using layers of beam splitter plugged on :math:`2^{(number\ of\ layers)}` threshold detectors. :param bs_layers: Number of beam splitter layers. Adding more layers improves the probability to detect multiple photons. :param reflectivity: Reflectivity of the beam splitters used to split photons. (defaults to 0.5) """ def __init__(self, bs_layers: int, reflectivity: float = 0.5): assert isinstance(bs_layers, int) and bs_layers > 0,\ f"Beam-splitter layers have to be a stricly positive integer (got {bs_layers})" assert 0 <= reflectivity <= 1, f"Reflectivity must be between 0 and 1 (got {reflectivity})" super().__init__() self.name = f"BS-PPNR{bs_layers}" self._layers = bs_layers self._r = reflectivity self._cache = {} # This cache records simulations for a given photon count to speed up computations @property def max_detections(self) -> int: """Maximum number of detected photons""" return 2 ** self._layers @property def type(self) -> DetectionType: return DetectionType.PPNR @property def efficiency(self) -> float: return 1 def clear_cache(self): """ Detector simulation results are cached in each instance and may consume memory. Call this method to empty the cache. """ self._cache = {} def create_circuit(self) -> Circuit: """ Creates the beam splitter layered circuit to simulate PPNR with threshold detectors. """ ppnr_circuit = Circuit(2 ** self._layers) for l in range(self._layers): perm_vector = list(range(0, 2**(l+1)-1, 2)) + list(range(1, 2**(l+1)-1, 2)) if len(perm_vector) > 1: ppnr_circuit.add(0, PERM(perm_vector)) for m in range(0, 2**(l+1), 2): ppnr_circuit.add(m, BS(BS.r_to_theta(self._r))) return ppnr_circuit def detect(self, theoretical_photons: int) -> BSDistribution | FockState: if theoretical_photons < 2: return FockState([theoretical_photons]) if theoretical_photons in self._cache: return self._cache[theoretical_photons] from perceval.backends import SLOSBackend ppnr_circuit = self.create_circuit() slos = SLOSBackend() slos.set_circuit(ppnr_circuit) slos.set_input_state(FockState([theoretical_photons] + [0]*(ppnr_circuit.m - 1))) dist = slos.prob_distribution() output = BSDistribution() for state, prob in dist.items(): state = state.threshold_detection() output[FockState([state.n])] += prob self._cache[theoretical_photons] = output return output class Detector(IDetector): """ Interleaved detector model -------------------------- Such a detector is made of one or multiple wires, each able to simultaneously detect a photon. Each photon hitting the detector is absorbed randomly by one of the wires. When photons hit the same wire, only one is detected. When they hit different wires, all are detected. The :code:`detect` method takes the number of wires into account to simulate the detection probability for each case. Having 1 wire makes the detector threshold, whereas having an infinity of them makes the detector perfectly PNR. :param n_wires: Number of detecting wires in the interleaved detector. (defaults to infinity) :param max_detections: Max number of photons the user is willing to read. The `|max_detection>` state would then mean "max_detection or more photons were detected". (defaults to None) :param wire_efficiency: Individual wire efficiency (defaults to 1) See :code:`pnr()`, :code:`threshold()` and :code:`ppnr(n_wires, max_detections, wire_efficiency)` static methods for easy detector initialization. Example: >>> from perceval.components import Detector >>> ppnr_detector = Detector.ppnr(5, 2) # Create a 5-wires interleaved detector, able to detect 1 or 2+ photons with unity efficiency >>> print(ppnr_detector.detect(3)) # and simulate the outcome of 3 photons hitting it at once { |1>: 0.04 |2>: 0.96 } """ def __init__(self, n_wires: int = None, max_detections: int = None, wire_efficiency: float = 1): super().__init__() assert n_wires is None or n_wires > 0, f"A detector requires at least 1 wire (got {n_wires})" assert max_detections is None or n_wires is None or max_detections <= n_wires, \ f"Max detections has to be lower or equal than the number of wires (got {max_detections} > {n_wires} wires)" assert wire_efficiency > 0 and wire_efficiency <= 1, f"Wire efficiency efficiency has to be between 0 and 1" self._wires = n_wires self._wire_efficiency = wire_efficiency self._max = max_detections if self._wires is not None: self._max = self._wires if max_detections is None else min(max_detections, self._wires) self._cache = {} @property def max_detections(self) -> int: """Maximum number of detected photons (None for infinity)""" return self._max @property def efficiency(self): """Wire efficiency""" return self._wire_efficiency @staticmethod def threshold() -> Detector: """Builds a threshold detector.""" d = Detector(1) d.name = "Threshold" return d @staticmethod def pnr() -> Detector: """Builds a perfect photon number resolving (PNR) detector.""" d = Detector() d.name = "PNR" return d @staticmethod def ppnr(n_wires: int = None, max_detections: int = None, wire_efficiency: float = 1) -> Detector: """Builds an interleaved pseudo-PNR detector.""" d = Detector(n_wires, max_detections, wire_efficiency) d.name = f"PPNR" return d @property def type(self) -> DetectionType: if self._max == 1 and self._wire_efficiency == 1: return DetectionType.Threshold elif self._wires is None and self._max is None and self._wire_efficiency == 1: return DetectionType.PNR return DetectionType.PPNR def detect(self, theoretical_photons: int) -> BSDistribution | FockState: detector_type = self.type if (theoretical_photons < 2 and self._wire_efficiency == 1) or detector_type == DetectionType.PNR: return FockState([theoretical_photons]) if detector_type == DetectionType.Threshold: return FockState([1]) if self._wires is None and self._wire_efficiency == 1: return FockState([min(theoretical_photons, self._max)]) if theoretical_photons in self._cache: return self._cache[theoretical_photons] remaining_p = 1 result = BSDistribution() max_detectable = min(self._max, theoretical_photons) if self._max is not None else theoretical_photons for i in range(0, max_detectable): p_i = self._cond_probability(i, theoretical_photons) result.add(FockState([i]), p_i) remaining_p -= p_i # The highest detectable photon count gains all the remaining probability result.add(FockState([max_detectable]), remaining_p) self._cache[theoretical_photons] = result return result @cache def _cond_probability(self, det: int, nph: int): """ The conditional probability of having `det` detections with `nph` photons on the total number of wires. This uses a recurrence formula set to compute each conditional probability from the ones with one less photon. Hitting `i` wires with `n` photons is: - hitting `i - 1` wires with `n - 1` photons AND hitting a new wire with the nth photon, with a successful absorption OR - hitting `i` wires with `n - 1` photons AND hitting one of the wire that were already hit with the nth photon, or an unsuccessful absorption """ if det == 0: return 1 if nph == 0 else (1 - self._wire_efficiency) ** nph if nph < det: return 0 if self._wires is None: return comb(nph, det) * self._wire_efficiency ** det * (1 - self._wire_efficiency) ** (nph - det) return self._cond_probability(det - 1, nph - 1) * (self._wires - det + 1) * self._wire_efficiency / self._wires \ + self._cond_probability(det, nph - 1) * (self._wire_efficiency * det / self._wires + 1 - self._wire_efficiency) def get_detection_type(detectors: list[IDetector]) -> DetectionType: """Computes a global detection type from a given list of detectors. :param detectors: List of detectors (None is treated as PNR). :return: * :code:`DetectionType.PNR` if all detectors are PNR or not set. * :code:`DetectionType.Threshold` if all detectors are threshold. * :code:`DetectionType.PPNR` if all detectors are PPNR. * else :code:`DetectionType.Mixed`. """ if not detectors: return DetectionType.PNR # To keep previous behavior where not setting any detector would mean PNR result = None for det in detectors: current = DetectionType.PNR if det is None else det.type # Default is PNR if result is None: result = current elif result != current: return DetectionType.Mixed return result def check_heralds_detectors(heralds: dict[int, int] | None, detectors: list[IDetector | None] | None) -> bool: """ Check that heralds are compatible with the given detectors. :param heralds: A dictionary mapping herald mode to its value. :param detectors: List of detectors (None is treated as PNR). :return: True if the maximum value for all detectors is bigger than the expected herald value """ if heralds and detectors: for k, v in heralds.items(): detector = detectors[k] if detector: max_val = detector.max_detections if max_val is not None and max_val < v: get_logger().warn(f"Incompatible heralds and detectors on mode {k}", channel.user) return False return True ================================================ FILE: perceval/components/experiment.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing import copy import weakref from dataclasses import dataclass from typing import Callable from multipledispatch import dispatch from perceval.utils import FockState, AnnotatedFockState, Parameter, PostSelect, LogicalState, NoiseModel, ModeType, StateVector, \ SVDistribution, NoisyFockState from perceval.utils.logging import get_logger, channel from perceval.utils.algorithms.simplification import perm_compose, simplify from ._mode_connector import ModeConnector, UnavailableModeException from .abstract_component import AComponent, AParametrizedComponent from .detector import IDetector, DetectionType, get_detection_type from .feed_forward_configurator import AFFConfigurator from .linear_circuit import Circuit, ACircuit from .non_unitary_components import TD from .port import Herald, PortLocation, APort, get_basic_state_from_ports from .unitary_components import Barrier, PERM, Unitary, PS @dataclass class _PhaseNoise: quantization: float = 0 max_error: float = 0 class Experiment: """ This class represents an optical table containing: - A circuit and/or components that represent the operations that will operate on photons. Can contain non-unitary components - The input state for the experiment. - Detectors to detect photons. - Ports to define groups of modes - Heralds - A post-selection method - A NoiseModel :param m_circuit: Number of spatial modes (int), first part of the circuit (Circuit) or None. If a circuit is passed, its size is used as the experiment size. :param noise: A `NoiseModel` :param name: The experiment name """ _no_copiable_attributes = { '_circuit_changed_observers', '_noise_changed_observers', '_input_changed_observers' } def __init__(self, m_circuit: int | ACircuit = None, noise: NoiseModel = None, name: str = "Experiment"): self._input_state = None self.name: str = name self._min_detected_photons_filter: int | None = None self._circuit_changed_observers: list[Callable[[Experiment | AComponent | None], None]] = [] self._noise_changed_observers: list[Callable[[], None]] = [] self._input_changed_observers: list[Callable[[], None]] = [] self.noise: NoiseModel | None = noise self._reset_circuit() self._init_circuit(m_circuit) def _reset_circuit(self): self._in_ports: dict = {} self._out_ports: dict = {} self._postselect: PostSelect | None = None self._is_unitary: bool = True self._has_td: bool = False self._has_feedforward = False self._anon_herald_num: int = 0 # This is not a herald count! self._components: list[tuple[tuple, AComponent]] = [] # Any type of components, not only unitary ones self._detectors: list[IDetector] = [] self.detectors_injected: list[int] = [] # List of modes where detectors are already in the circuit self._in_mode_type: list[ModeType] = [] self._out_mode_type: list[ModeType] = [] self._m: int = 0 # Circuit size @property def is_unitary(self) -> bool: """ :return: True if the circuit is composed of only unitary components, False otherwise. """ return self._is_unitary @property def has_td(self) -> bool: """ :return: True if the circuit contains at least one time delay, False otherwise. """ return self._has_td @property def has_feedforward(self) -> bool: """ :return: True if the circuit contains at least one feed-forward layer, False otherwise. """ return self._has_feedforward def _init_circuit(self, m_circuit: ACircuit | int): if isinstance(m_circuit, ACircuit): self.m = m_circuit.m self.add(0, m_circuit) elif m_circuit is not None: self.m = m_circuit # number of modes def clear_input_and_circuit(self, new_m=None): get_logger().debug(f"Clear input and circuit in experiment {self.name}", channel.general) self._reset_circuit() self._input_state = None self._input_changed() self._circuit_changed() if new_m is not None: self.m = new_m def _circuit_changed(self, component=None): for observer in self._circuit_changed_observers: observer_fn = observer() if observer_fn is not None: observer_fn(component) # Used to notify the Processors containing this experiment of a new component def add_observers(self, circuit_observer: callable, noise_observer: callable, input_observer: callable): self._circuit_changed_observers.append(weakref.WeakMethod(circuit_observer)) self._noise_changed_observers.append(weakref.WeakMethod(noise_observer)) self._input_changed_observers.append(weakref.WeakMethod(input_observer)) def min_detected_photons_filter(self, n: int): r""" Sets-up a state post-selection on the number of detected photons. With thresholded detectors, this will actually filter on "click" count. :param n: Minimum expected photons This post-selection has an impact on the output physical performance """ self._min_detected_photons_filter = n @property def min_photons_filter(self): return self._min_detected_photons_filter @property def input_state(self): return self._input_state @property def noise(self): return self._noise @noise.setter def noise(self, nm: NoiseModel | None): if nm is not None: self._phase_noise = _PhaseNoise(nm.phase_imprecision, nm.phase_error) else: self._phase_noise = _PhaseNoise() if nm is None or isinstance(nm, NoiseModel): self._noise = nm for observer in self._noise_changed_observers: observer_fn = observer() if observer_fn is not None: observer_fn() else: raise TypeError("noise type has to be 'NoiseModel'") @property def post_select_fn(self): return self._postselect def set_postselection(self, postselect: PostSelect): r""" Set a logical post-selection function. Along with the heralded modes, this function has an impact on the logical performance of the processor holding this experiment :param postselect: Sets a post-selection function. Its signature must be `func(s: BasicState) -> bool`. If None is passed as parameter, removes the previously defined post-selection function. """ if not isinstance(postselect, PostSelect): raise TypeError("Parameter must be a PostSelect object") self._circuit_changed() self._postselect = postselect def clear_postselection(self): if self._postselect is not None: self._circuit_changed() self._postselect = None def __deepcopy__(self, memo): if id(self) in memo: return memo[id(self)] cls = self.__class__ obj = cls.__new__(cls) memo[id(self)] = obj for k, v in self.__dict__.items(): if k in cls._no_copiable_attributes: setattr(obj, k, []) else: setattr(obj, k, copy.deepcopy(v, memo)) pass return obj def copy(self) -> Experiment: """ Performs a deep copy of the current experiment. :return: A copy of this experiment. """ get_logger().debug(f"Copy experiment {self.name}", channel.general) return copy.deepcopy(self) def set_circuit(self, circuit: ACircuit): r""" Removes all components and replace them by the given circuit. :param circuit: The circuit to start the experiment with :return: Self to allow direct chain this with .add() """ if self._m == 0: self.m = circuit.m assert circuit.m == self.circuit_size, "Circuit doesn't have the right number of modes" self._components = [] for r, c in circuit: self._components.append((r, c)) return self def add(self, mode_mapping, component, keep_port: bool = True): """ Add a component to the experiment (unitary or non-unitary). :param mode_mapping: Describe how the new component is connected to the existing experiment. Can be: * an int: composition uses consecutive modes starting from `mode_mapping` * a list or a dict: describes the full mapping of length the input mode count of `component` :param component: The component to append to the experiment. Can be: * A unitary circuit * A non-unitary component * A processor * An experiment * A detector :param keep_port: if True, saves `self`'s output ports on modes impacted by the new component, otherwise removes them. Adding a component on non-ordered, non-consecutive modes computes the right permutation (PERM component) which fits into the existing experiment and the new component. Example: >>> e = Experiment(6) >>> e.add(0, BS()) # Modes (0, 1) connected to (0, 1) of the added beam splitter >>> e.add([2,5], BS()) # Modes (2, 5) of the experiment's output connected to (0, 1) of the added beam splitter >>> e.add({2:0, 5:1}, BS()) # Same as above If the added component is a processor or an experiment with modes having heralds only on one side, no permutation will be added at the end, and the "in-between" modes will be pushed to the bottom. """ if self.m == 0: self.m = component.m + mode_mapping if isinstance(mode_mapping, int) else max(mode_mapping) + 1 get_logger().debug(f"Number of modes of interest defaulted to {self.m} in experiment {self.name}", channel.general) from perceval import AProcessor # This is ugly but necessary to keep the user interface if isinstance(component, AProcessor): component = component.experiment connector = ModeConnector(self, component, mode_mapping) if isinstance(component, Experiment): self._compose_experiment(connector, component, keep_port) elif isinstance(component, IDetector): self._add_detector(mode_mapping, component) elif isinstance(component, AFFConfigurator): self._add_ffconfig(mode_mapping, component) elif isinstance(component, Barrier): if isinstance(mode_mapping, int): mode_mapping = tuple(range(mode_mapping, mode_mapping + component.m)) self._components.append((mode_mapping, component)) elif isinstance(component, AComponent): self._add_component(connector.resolve(), component, keep_port) else: raise RuntimeError(f"Cannot add {type(component)} object to a Processor") self._circuit_changed(component) return self def _add_ffconfig(self, modes, component: AFFConfigurator): if isinstance(modes, int): modes = tuple(range(modes, modes + component.m)) # Check composition consistency if min(modes) < 0 or max(modes) >= self.m: raise ValueError(f"Mode numbers must be in [0; {self.m - 1}] (got {modes})") if any([self._out_mode_type[i] != ModeType.CLASSICAL for i in modes]): raise UnavailableModeException(modes, "Cannot add a classical component on non-classical modes") photonic_modes = component.config_modes(modes) if min(photonic_modes) < 0 or max(photonic_modes) >= self.m: raise ValueError(f"Mode numbers must be in [0; {self.m - 1}] (got {photonic_modes})") if any([self._out_mode_type[i] != ModeType.PHOTONIC for i in photonic_modes]): raise UnavailableModeException(photonic_modes, "Cannot add a configured circuit on non-photonic modes") self._validate_new_parameters({p.name: p for p in component.get_parameters(False, True)}) modes_add_detectors = [m for m in modes if m not in self.detectors_injected] self._components.append((tuple(range(self.m)), Barrier(self.m, visible=len(modes_add_detectors) == 0))) if modes_add_detectors and modes_add_detectors[0] > 0: # Barrier above detectors ports = tuple(range(0, modes_add_detectors[0])) self._components.append((ports, Barrier(len(ports), visible=True))) if modes_add_detectors and modes_add_detectors[-1] < self.m - 1: # Barrier below detectors ports = tuple(range(modes_add_detectors[-1] + 1, self.m)) self._components.append((ports, Barrier(len(ports), visible=True))) for m in modes_add_detectors: self.detectors_injected.append(m) self._components.append(((m,), self._detectors[m])) self._components.append((modes, component)) self._has_feedforward = True self._is_unitary = False component.block_circuit_size() # User cannot add larger photonic circuit output from now on def _add_detector(self, mode: int, detector: IDetector): if isinstance(mode, (tuple, list)) and len(mode) == 1: mode = mode[0] if not isinstance(mode, int): raise TypeError(f"When adding a detector, the mode number must be an integer (got {type(mode)})") if self._out_mode_type[mode] == ModeType.CLASSICAL: raise UnavailableModeException(mode, "Mode is not photonic, cannot plug a detector.") self._detectors[mode] = detector if self._out_mode_type[mode] == ModeType.PHOTONIC: self._out_mode_type[mode] = ModeType.CLASSICAL @property def detectors(self): """ :return: The list of detectors which were defined in the experiment. """ return self._detectors def _validate_postselect_composition(self, mode_mapping: dict): if self._postselect is not None and isinstance(self._postselect, PostSelect): impacted_modes = list(mode_mapping.keys()) # can_compose_with can take a bit of time so leave this test as an assert which can be removed by -O assert self._postselect.can_compose_with(impacted_modes), \ f"Post-selection conditions cannot compose with modes {impacted_modes}" def _validate_new_parameters(self, new_params: dict[str, Parameter]): self_params = self.get_circuit_parameters() for _, param in new_params.items(): if not param.fixed: for internal_p in param._params: if internal_p.name in self_params and internal_p is not self_params[internal_p.name]: raise RuntimeError(f"The experiment already owns a parameter named {internal_p.name}") def _compose_experiment(self, connector: ModeConnector, experiment: Experiment, keep_port: bool): self._validate_new_parameters(experiment.get_circuit_parameters()) get_logger().debug(f"Compose experiment {self.name} with {experiment.name}", channel.general) self._is_unitary = self._is_unitary and experiment._is_unitary self._has_td = self._has_td or experiment._has_td if experiment.heralds: # adding the same experiment component again renders incorrect heralds if not copied # This concerns our gate based processors from catalog which has no input params get_logger().debug(" Force copy during experiment compose", channel.general) experiment = experiment.copy() mode_mapping = connector.resolve() get_logger().debug(f" Resolved mode mapping to {mode_mapping} during experiment compose", channel.general) is_symmetrical = experiment.in_heralds.keys() == experiment.heralds.keys() # Compute new herald positions n_new_heralds = connector.add_heralded_modes(mode_mapping) self._validate_postselect_composition(mode_mapping) if not keep_port: # Remove output ports used to connect the new experiment for i in mode_mapping: port = self.get_output_port(i) if port is not None: del self._out_ports[port] self._in_mode_type += [ModeType.HERALD] * n_new_heralds # New input heralds are always put at the bottom self._m += n_new_heralds if is_symmetrical: self._out_mode_type += [ModeType.HERALD] * n_new_heralds for m_herald in experiment.heralds: self._detectors += [experiment._detectors[m_herald]] # Check port composition for m_out, m_in in mode_mapping.items(): out_port = self.get_output_port(m_out) in_port = experiment.get_input_port(m_in) if (out_port is not None and in_port is not None and (out_port.encoding != in_port.encoding or [mode_mapping.get(i, i) for i in self._out_ports[out_port]] != experiment._in_ports[in_port])): get_logger().warn( f"The composition of {self.name} ({out_port.encoding} on modes {self._out_ports[out_port]}) " f"with {experiment.name} ({in_port.encoding} on modes {experiment._in_ports[in_port]}) " f"will lead to unexpected results.") break # Add PERM, component, (PERM^-1 if is_symmetrical) perm_modes, perm_component = connector.generate_permutation(mode_mapping) new_components = [] if perm_component is not None: get_logger().debug( f" Add {perm_component.perm_vector} permutation before experiment compose", channel.general) if len(self._components) > 0 and isinstance(self._components[-1][1], PERM): # Simplify composition by merging two consecutive PERM components l_perm_r = self._components[-1][0] l_perm_vect = self._components[-1][1].perm_vector new_range, new_perm_vect = perm_compose(l_perm_r, l_perm_vect, perm_modes, perm_component.perm_vector) new_components.append((new_range, PERM(new_perm_vect))) self._components.pop(-1) else: new_components.append((perm_modes, perm_component)) for pos, c in experiment.components: pos = [x + min(mode_mapping) for x in pos] new_components.append((pos, c)) if perm_component is not None and is_symmetrical: perm_inv = perm_component.copy() perm_inv.inverse(h=True) get_logger().debug(f" Add {perm_inv.perm_vector} permutation after experiment compose", channel.general) new_components.append((perm_modes, perm_inv)) elif not is_symmetrical: # We need to apply the permutation on the detectors and mode types self._out_mode_type = connector.compose_lists(mode_mapping, self._out_mode_type, experiment._out_mode_type) self._detectors = connector.compose_lists(mode_mapping, self._detectors, experiment._detectors) self_ports = [None] * self.circuit_size for port, port_range in self._out_ports.items(): self_ports[port_range[0]] = port other_ports = [None] * experiment.circuit_size for port, port_range in experiment._out_ports.items(): other_ports[port_range[0]] = port self._out_ports = {} out_ports = connector.compose_lists(mode_mapping, self_ports, other_ports) for port_mode, port in enumerate(out_ports): if isinstance(port, Herald): self.add_herald(port_mode, port.expected, port.user_given_name, PortLocation.OUTPUT) elif port is not None: if self.are_modes_free(range(port_mode, port_mode + port.m)): self.add_port(port_mode, port, PortLocation.OUTPUT) new_components = simplify(new_components, self.circuit_size) self._components += new_components # Retrieve ports from the other experiment # Output ports if is_symmetrical: for port, port_range in experiment._out_ports.items(): port_mode = list(mode_mapping.keys())[list(mode_mapping.values()).index(port_range[0])] if isinstance(port, Herald): self.add_herald(port_mode, port.expected, port.user_given_name, PortLocation.OUTPUT) else: if self.are_modes_free(range(port_mode, port_mode + port.m)): self.add_port(port_mode, port, PortLocation.OUTPUT) # Input ports for port, port_range in experiment._in_ports.items(): port_mode = list(mode_mapping.keys())[list(mode_mapping.values()).index(port_range[0])] if isinstance(port, Herald): self.add_herald(port_mode, port.expected, port.user_given_name, PortLocation.INPUT) else: if self.are_modes_free(range(port_mode, port_mode + port.m), PortLocation.INPUT): self.add_port(port_mode, port, PortLocation.INPUT) # Detectors if is_symmetrical: for m in range(experiment.circuit_size): # The heralded modes detectors have already been added at the bottom modes d = experiment.detectors[m] if m not in experiment.heralds and d is not None: new_mode = list(mode_mapping.keys())[list(mode_mapping.values()).index(m)] self._detectors[new_mode] = d if self._postselect is not None and perm_component is not None and not is_symmetrical: c_first = perm_modes[0] self._postselect.apply_permutation(perm_component.perm_vector, c_first) # Retrieve post process function from the other experiment if experiment._postselect is not None: c_first = perm_modes[0] other_postselect = copy.copy(experiment._postselect) if perm_component is not None and is_symmetrical: other_postselect.apply_permutation(perm_inv.perm_vector, c_first) other_postselect.shift_modes(c_first) if not (self._postselect is None or other_postselect is None or self._postselect.is_independent_with(other_postselect)): raise RuntimeError("Cannot automatically compose experiment's post-selection conditions") self._postselect = self._postselect or PostSelect() self._postselect.merge(other_postselect) def _add_component(self, mode_mapping, component, keep_port: bool): self._validate_postselect_composition(mode_mapping) if isinstance(component, AParametrizedComponent): self._validate_new_parameters({p.name: p for p in component.get_parameters(False, True)}) if not keep_port: # Remove output ports used to connect the new experiment for i in mode_mapping: port = self.get_output_port(i) if port is not None: del self._out_ports[port] perm_modes, perm_component = ModeConnector.generate_permutation(mode_mapping) if perm_component is not None: self._components.append((perm_modes, perm_component)) sorted_modes = tuple(range(min(mode_mapping), min(mode_mapping) + component.m)) self._components.append((sorted_modes, component)) if perm_component is not None: perm_inv = perm_component.copy() perm_inv.inverse(h=True) self._components.append((perm_modes, perm_inv)) self._is_unitary = self._is_unitary and isinstance(component, ACircuit) self._has_td = self._has_td or isinstance(component, TD) def _add_herald(self, mode, herald: Herald, location: PortLocation = PortLocation.IN_OUT): if location == PortLocation.INPUT or location == PortLocation.IN_OUT: self._in_ports[herald] = [mode] self._in_mode_type[mode] = ModeType.HERALD if location == PortLocation.OUTPUT or location == PortLocation.IN_OUT: self._out_ports[herald] = [mode] self._out_mode_type[mode] = ModeType.HERALD self._circuit_changed() def add_herald(self, mode: int, expected: int, name: str = None, location: PortLocation = PortLocation.IN_OUT): r""" Add a heralded mode :param mode: Mode index of the herald :param expected: number of expected photon as input AND output on the given mode (must be 0 or 1) :param name: Herald port name. If none is passed, the name is auto-generated :param location: Port location of the herald (input, output or both) """ if not self.are_modes_free([mode], location): raise UnavailableModeException(mode, "Another port overlaps") if name is None: name = self._anon_herald_num self._anon_herald_num += 1 herald = Herald(expected, name) self._add_herald(mode, herald, location) return self @property def components(self): return self._components @property def m(self) -> int: """ :return: Number of modes of interest (MOI) at the output of the experiment """ return self._m - len(self.heralds) @property def m_in(self): """ :return: Number of modes of interest (MOI) at the input the experiment """ return self._m - len(self.in_heralds) @m.setter def m(self, value: int): """Setter for the circuit size""" if self._m != 0: raise RuntimeError(f"The number of modes of this experiment was already set (to {self._m})") if not isinstance(value, int) or value < 1: raise ValueError(f"The number of modes should be a strictly positive integer (got {value})") self._m = value self._detectors = [None] * value self._in_mode_type = [ModeType.PHOTONIC] * value self._out_mode_type = [ModeType.PHOTONIC] * value @property def circuit_size(self) -> int: r""" :return: Total size of the enclosed circuit (i.e. self.m + ancillary mode count) """ return self._m def unitary_circuit(self, flatten: bool = False, use_phase_noise: bool = False) -> Circuit: """ Creates a unitary circuit from internal components, if all internal components are unitary. :param flatten: if True, the component recursive hierarchy is discarded, making the output circuit "flat". """ if not self._is_unitary: raise RuntimeError("Cannot retrieve a unitary circuit because some components are non-unitary") circuit = Circuit(self.circuit_size) for pos_m, component in self._components: circuit.add(pos_m, component, merge=flatten) noise = self._phase_noise if not use_phase_noise or not (noise.max_error or noise.quantization): return circuit # Apply phase quantization noise on all phase parameters in the circuit get_logger().debug(f"Inject {noise} in the circuit") circuit = circuit.copy() # Copy the whole circuit in order to keep the initial phase values in self for _, component in circuit: if not isinstance(component, PS): continue if noise.max_error is not None: err_param = component.param("max_error") if not err_param.is_variable and float(err_param) == 0: err_param.set_value(noise.max_error, force=True) if noise.quantization: phi_param = component.param("phi") phi_param.set_value(noise.quantization * round(float(phi_param) / noise.quantization), force=True) return circuit def non_unitary_circuit(self, flatten: bool = False) -> list[tuple[tuple, AComponent]]: if self._has_td: # Inherited from the parent experiment in this case return self.components comp = _flatten(self) if flatten: return comp # Compute the unitaries between the non-unitary components new_comp = [] unitary_circuit = Circuit(self.circuit_size) min_r = self.circuit_size max_r = 0 for r, c in comp: if isinstance(c, ACircuit): unitary_circuit.add(r, c) min_r = min(min_r, r[0]) max_r = max(max_r, r[-1] + 1) else: if unitary_circuit.ncomponents(): new_comp.append((tuple(r_i for r_i in range(min_r, max_r)), Unitary(unitary_circuit.compute_unitary()[min_r:max_r, min_r:max_r]))) unitary_circuit = Circuit(self.circuit_size) min_r = self.circuit_size max_r = 0 new_comp.append((r, c)) if unitary_circuit.ncomponents(): new_comp.append((tuple(r_i for r_i in range(min_r, max_r)), Unitary(unitary_circuit.compute_unitary()[min_r:max_r, min_r:max_r]))) return new_comp def get_circuit_parameters(self) -> dict[str, Parameter]: return {p.name: p for _, c in self._components if isinstance(c, AParametrizedComponent) for p in c.get_parameters()} @property def out_port_names(self): r""" :return: A list of the output port names. Names are repeated for ports connected to more than one mode """ result = [''] * self.circuit_size for port, m_range in self._out_ports.items(): for m in m_range: result[m] = port.name return result @property def in_port_names(self): r""" :return: A list of the input port names. Names are repeated for ports connected to more than one mode """ result = [''] * self.circuit_size for port, m_range in self._in_ports.items(): for m in m_range: result[m] = port.name return result def add_port(self, m, port: APort, location: PortLocation = PortLocation.IN_OUT): port_range = list(range(m, m + port.m)) assert port.supports_location(location), f"Port is not compatible with location '{location.name}'" if isinstance(port, Herald): if not self.are_modes_free([m], location): raise UnavailableModeException(m, "Another port overlaps") self._add_herald(m, port, location) return self if location == PortLocation.IN_OUT or location == PortLocation.INPUT: if not self.are_modes_free(port_range, PortLocation.INPUT): raise UnavailableModeException(port_range, "Another port overlaps") self._in_ports[port] = port_range if location == PortLocation.IN_OUT or location == PortLocation.OUTPUT: if not self.are_modes_free(port_range, PortLocation.OUTPUT): raise UnavailableModeException(port_range, "Another port overlaps") self._out_ports[port] = port_range return self @staticmethod def _find_and_remove_port_from_list(m, port_list) -> bool: for current_port, current_port_range in port_list.items(): if m in current_port_range: del port_list[current_port] return True return False def remove_port(self, m, location: PortLocation = PortLocation.IN_OUT): if location in (PortLocation.IN_OUT, PortLocation.INPUT): if not self._find_and_remove_port_from_list(m, self._in_ports): raise UnavailableModeException(m, f"Port is not at location '{location.name}'") if location in (PortLocation.IN_OUT, PortLocation.OUTPUT): if not self._find_and_remove_port_from_list(m, self._out_ports): raise UnavailableModeException(m, f"Port is not at location '{location.name}'") return self def is_mode_connectible(self, mode: int) -> bool: if mode < 0: return False if mode >= self.circuit_size: return False return self._out_mode_type[mode] == ModeType.PHOTONIC def are_modes_free(self, mode_range, location: PortLocation = PortLocation.OUTPUT) -> bool: """ :return: True if all modes in mode_range are free of ports, for a given location (input, output or both) """ if location == PortLocation.IN_OUT or location == PortLocation.INPUT: for m in mode_range: if self.get_input_port(m) is not None: return False if location == PortLocation.IN_OUT or location == PortLocation.OUTPUT: for m in mode_range: if self.get_output_port(m) is not None: return False return True def get_input_port(self, mode): for port, mode_range in self._in_ports.items(): if mode in mode_range: return port return None def get_output_port(self, mode): for port, mode_range in self._out_ports.items(): if mode in mode_range: return port return None @property def detection_type(self) -> DetectionType: return get_detection_type(self._detectors) @property def heralds(self) -> dict[int, int]: return {port_range[0]: port.expected for port, port_range in self._out_ports.items() if isinstance(port, Herald)} @property def in_heralds(self) -> dict[int, int]: return {port_range[0]: port.expected for port, port_range in self._in_ports.items() if isinstance(port, Herald)} def check_input(self, input_state: FockState): r"""Check if a basic state input matches with the current experiment configuration""" assert self.m_in, "A circuit has to be set before the input state" expected_input_length = self.m_in assert len(input_state) == expected_input_length, \ f"Input length not compatible with circuit (expects {expected_input_length}, got {len(input_state)})" def _input_changed(self): for observer in self._input_changed_observers: observer_fn = observer() if observer_fn is not None: observer_fn() @dispatch(LogicalState) def with_input(self, input_state: LogicalState): input_state = get_basic_state_from_ports(list(self._in_ports.keys()), input_state) if self._min_detected_photons_filter is None: self._min_detected_photons_filter = input_state.n self.with_input(input_state) @dispatch(FockState) def with_input(self, input_state: FockState) -> None: self.check_input(input_state) input_list = [0] * self.circuit_size input_idx = 0 # Build real input state (merging ancillas + expected input) and compute expected photon count for k in range(self.circuit_size): if k in self.in_heralds: input_list[k] = self.in_heralds[k] else: input_list[k] = input_state[input_idx] input_idx += 1 self._input_state = FockState(input_list) self._input_changed() @dispatch(AnnotatedFockState) def with_input(self, input_state: AnnotatedFockState) -> None: if input_state.has_polarization: self._input_state = input_state self._input_changed() else: raise TypeError("Local simulations only support AnnotatedFockState in case of a polarized input state") @dispatch((StateVector, NoisyFockState)) def with_input(self, sv: StateVector | NoisyFockState): r""" Setting directly state vector or NoisyFockState as input of a experiment, use SVDistribution input :param sv: the state vector """ self.with_input(SVDistribution(sv)) @dispatch(SVDistribution) def with_input(self, svd: SVDistribution): r""" Processor input can be set 100% manually via a state vector distribution, bypassing the source. :param svd: The input SVDistribution which won't be changed in any way by the source. Every state vector size has to be equal to `self.circuit_size` """ assert self.m is not None, "A circuit has to be set before the input distribution" assert svd.m == self.circuit_size, f'Input distribution contains states with a bad size ({svd.m}), expected {self.circuit_size}' self._input_state = svd self._input_changed() def flatten(self, max_depth=None) -> list[tuple]: """ List all the components in the experiment where recursive circuits have been flattened. :param max_depth: The maximum depth of recursion. The remaining sub-circuits at this depth are listed as a component. """ return _flatten(self, max_depth=max_depth) def _flatten(composite, starting_mode=0, max_depth=None) -> list[tuple]: component_list = [] for m_range, comp in composite._components: if isinstance(comp, Circuit): if max_depth is None or max_depth > 0: sub_list = _flatten(comp, starting_mode=m_range[0], max_depth=max_depth - 1 if max_depth is not None else None) component_list += sub_list else: m_range = [m + starting_mode for m in m_range] component_list.append((m_range, comp)) else: m_range = [m + starting_mode for m in m_range] component_list.append((m_range, comp)) return component_list ================================================ FILE: perceval/components/feed_forward_configurator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing from abc import ABC, abstractmethod from .unitary_components import Unitary from .abstract_component import AParametrizedComponent from .linear_circuit import ACircuit from perceval.utils import BasicState, Matrix class AFFConfigurator(AParametrizedComponent, ABC): DEFAULT_NAME = "FFC" """ Abstract feed-forward configurator. :param m: The number of classical modes that are detected (after a detector) :param offset: The distance between the configurator and the first mode of the implemented circuits. For positive values, it is the number of empty modes between the configurator and the configured circuit below. For negative values, it is the same but the circuit is located above the configurator (the number of empty modes is abs(`offset`) - 1, so an offset of -1 means that there is no empty modes between the configurator and the circuit). All circuits are considered to have the size of the biggest possible circuit in this configurator. :param default_circuit: The circuit to be used if the measured state does not befall into one of the declared cases """ def __init__(self, m: int, offset: int, default_circuit: ACircuit, name: str = None): super().__init__(m, name) self._offset: int = offset self.default_circuit = default_circuit self._max_circuit_size = default_circuit.m self._blocked_circuit_size: bool = False def block_circuit_size(self): """Call this to prevent adding circuits bigger than the current maximum size""" self._blocked_circuit_size = True @abstractmethod def configure(self, measured_state: BasicState) -> ACircuit: """ Gives the circuit or processor that must be configured given the measured state :param measured_state: The state of size self.m that corresponds to the measurements of the modes on which the configurator is located. :return: The processor or circuit that must be set """ pass @abstractmethod def circuit_template(self) -> ACircuit: """ Return a fitting representation of the controlled circuit or processor. """ def config_modes(self, self_modes: tuple[int, ...]) -> tuple[int, ...]: """ Gives the list of modes on which to place the circuit :param self_modes: The tuple containing the modes on which the configurator is located, in crescent order """ assert len(self_modes) == self.m, "Incorrect number of modes" if self._offset >= 0: first_mode = self_modes[-1] + 1 + self._offset return tuple(range(first_mode, first_mode + self._max_circuit_size)) last_mode = self_modes[0] + self._offset return tuple(range(last_mode - self._max_circuit_size + 1, last_mode + 1)) @property def circuit_offset(self): return self._offset @circuit_offset.setter def circuit_offset(self, offset: int): assert isinstance(offset, int), f"A feed-forward configurator offset must be an integer (received {offset})" self._offset = offset class FFCircuitProvider(AFFConfigurator): DEFAULT_NAME = "FFC" """ For any measurement, FFCircuitProvider will return a circuit or a processor, picked from known mapping of configurations. Each configuration links a measurement to a circuit or processor. If a measurement is received and was not set in the mapping, a mandatory default circuit or processor is returned. :param m: The number of classical modes that are detected (after a detector) :param offset: The distance between the configurator and the first mode of the implemented circuits. For positive values, it is the number of empty modes between the configurator and the configured circuit below. For negative values, it is the same but the circuit is located above the configurator (the number of empty modes is abs(`offset`) - 1, so an offset of -1 means that there is no empty modes between the configurator and the circuit). All circuits are considered to have the size of the biggest possible circuit in this configurator. :param default_circuit: The circuit to be used if the measured state does not befall into one of the declared cases """ def __init__(self, m: int, offset: int, default_circuit: ACircuit, name: str = None): assert not isinstance(default_circuit, AFFConfigurator), \ "Can't add directly a Feed-forward configurator to a configurator (use a Processor)" super().__init__(m, offset, default_circuit, name) self._params = self._get_parameters(default_circuit, True, True) self._map: dict[BasicState, ACircuit] = {} @staticmethod def _get_parameters(circ, all_params: bool, expressions: bool) -> dict: if isinstance(circ, ACircuit): res = {p.name: p for p in circ.get_parameters(all_params, expressions)} else: # This is a Processor or an Experiment res = {} for _, c in circ.components: if isinstance(c, AParametrizedComponent): res.update({p.name: p for p in c.get_parameters(all_params, expressions)}) return res def reset_map(self): self._max_circuit_size = self.default_circuit.m self._map = {} self._params = self._get_parameters(self.default_circuit, True, True) @property def circuit_map(self): return self._map @circuit_map.setter def circuit_map(self, circuit_map: dict[BasicState, ACircuit]): self.reset_map() for state, circ in circuit_map.items(): self.add_configuration(state, circ) def add_configuration(self, state, circuit: ACircuit) -> FFCircuitProvider: state = BasicState(state) assert state.m == self.m, f"Incorrect number of modes for state {state} (expected {self.m})" assert not isinstance(circuit, AFFConfigurator), \ "Can't add directly a Feed-forward configurator to a configurator (use a Processor)" if not self._blocked_circuit_size: self._max_circuit_size = max(self._max_circuit_size, circuit.m) else: if circuit.m != self._max_circuit_size: raise RuntimeError(f"Circuit size mismatch (got {circuit.m}, expected {self._max_circuit_size} modes)") from .abstract_processor import AProcessor if isinstance(circuit, AProcessor): circuit = circuit.experiment params = self._get_parameters(circuit, False, True) for _, param in params.items(): if not param.fixed: for internal_p in param._params: if internal_p.name in self._params and internal_p is not self._params[internal_p.name]: raise RuntimeError(f"two parameters with the same name in the circuit {internal_p.name}") self._params.update(params) self._map[state] = circuit return self def configure(self, measured_state: BasicState) -> ACircuit: return self.circuit_map.get(measured_state, self.default_circuit) def circuit_template(self) -> ACircuit: return Unitary(Matrix.eye(self.default_circuit.m), f"U({self.name})") class FFConfigurator(AFFConfigurator): DEFAULT_NAME = "FFC" """ This class relies on a mapping between detections and a mapping of variable names and numerical values, controlling a circuit template. :param m: The number of classical modes that are detected (after a detector) :param offset: The distance between the configurator and the first mode of the implemented circuits. For positive values, it is the number of empty modes between the configurator and the configured circuit below. For negative values, it is the same but the circuit is located above the configurator (the number of empty modes is abs(`offset`) - 1, so an offset of -1 means that there is no empty modes between the configurator and the circuit). All circuits are considered to have the size of the biggest possible circuit in this configurator. :param controlled_circuit: A circuit containing symbolic parameters whose value will be changed depending on the measured state. :param default_config: A dictionary mapping the parameters of the circuit and their value to use in case a measured state does not befall into one of the declared cases. """ def __init__(self, m: int, offset: int, controlled_circuit: ACircuit, default_config: dict[str, float], name: str = None): if not isinstance(controlled_circuit, ACircuit): raise TypeError(f"controlled_circuit must be of type ACircuit") self._controlled = controlled_circuit self._linked_vars = self._controlled.vars self._configs: dict[BasicState, dict[str, float]] = {} self._check_configuration(default_config) self._default_config = default_config default_circuit = controlled_circuit.copy() default_circuit.assign(default_config) super().__init__(m, offset, default_circuit, name) def _check_configuration(self, config: dict[str, float]): if len(config) != len(self._linked_vars): raise ValueError( f"Wrong parameter count in the configuration ({len(config)}, expected {len(self._linked_vars)})") for param_name in config: if param_name not in self._linked_vars: raise NameError(f"Parameter {param_name} does not exist in the controlled circuit") def add_configuration(self, detections: BasicState | tuple[int, ...], config: dict[str, float]) -> FFConfigurator: detections = BasicState(detections) if detections.m != self.m: raise ValueError(f"Wrong size for detections; got {len(detections)}, expected the number of modes plugged-in, i.e. {self.m}") self._check_configuration(config) self._configs[detections] = config return self def configure(self, measured_state: BasicState) -> ACircuit: if measured_state not in self._configs: return self.default_circuit circuit = self._controlled.copy() circuit.assign(self._configs[measured_state]) return circuit def circuit_template(self) -> ACircuit: return self._controlled ================================================ FILE: perceval/components/generic_interferometer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math from collections.abc import Callable from .linear_circuit import ACircuit, Circuit from .unitary_components import Barrier from perceval.utils import InterferometerShape from perceval.utils.logging import get_logger, channel class GenericInterferometer(Circuit): r"""Generate a generic interferometer circuit with generic elements and optional phase_shifter layer :param m: number of modes :param fun_gen: generator function for the building components, index is an integer allowing to generate named parameters - for instance: :code:`fun_gen=lambda idx: pcvl.BS()//(0, pcvl.PS(pcvl.P(f"phi_{idx}")))` :param shape: The output interferometer shape (InterferometerShape.RECTANGLE or InterferometerShape.TRIANGLE) :param depth: if None, maximal depth is :math:`m-1` for rectangular shape, :math:`m` for triangular shape. Can be used with :math:`2*m` to reproduce :cite:`fldzhyan2020optimal`. :param phase_shifter_fun_gen: a function generating a phase_shifter circuit. :param phase_at_output: if True creates a layer of phase shifters at the output of the generated interferometer else creates it in the input (default: False) :param upper_component_gen: generator function for the building the upper component, index is an integer allowing to generate named parameters - for instance: :code:`upper_component_gen=lambda idx: pcvl.PS(pcvl.P(f"phi_upper_{idx}"))` :param lower_component_gen: generator function for the building the lower component, index is an integer allowing to generate named parameters - for instance: :code:`lower_component_gen=lambda idx: pcvl.PS(pcvl.P(f"phi_lower_{idx}"))` See :cite:`fldzhyan2020optimal`, :cite:`clements2016optimal` and :cite:`reck1994experimental` """ def __init__(self, m: int, fun_gen: Callable[[int], ACircuit], shape: InterferometerShape = InterferometerShape.RECTANGLE, depth: int = None, phase_shifter_fun_gen: Callable[[int], ACircuit] = None, phase_at_output: bool = False, upper_component_gen: Callable[[int], ACircuit] = None, lower_component_gen: Callable[[int], ACircuit] = None, align_with_barriers: bool = True): assert isinstance(shape, InterferometerShape),\ f"Wrong type for shape, expected InterferometerShape, got {type(shape)}" super().__init__(m) self._shape = shape self._depth = depth self._depth_per_mode = [0] * m self._pattern_generator = fun_gen self._has_input_phase_layer = False self._upper_component_gen = upper_component_gen self._lower_component_gen = lower_component_gen self._use_barriers = align_with_barriers if phase_shifter_fun_gen and not phase_at_output: self._has_input_phase_layer = True for i in range(0, m): self.add(i, phase_shifter_fun_gen(i), merge=True) if shape == InterferometerShape.RECTANGLE: self._build_rectangle() elif shape == InterferometerShape.TRIANGLE: if upper_component_gen or lower_component_gen: get_logger().warn(f"upper_component_gen or lower_component_gen cannot be applied for shape {shape}") self._build_triangle() else: raise NotImplementedError(f"Shape {shape} not supported") self._has_output_phase_layer = False if phase_shifter_fun_gen and phase_at_output: self._has_output_phase_layer = True for i in range(0, m): self.add(i, phase_shifter_fun_gen(i)) def __repr__(self): return f"Generic interferometer ({self.m} modes, {str(self._shape.name)}, {self.ncomponents()} components)" @property def mzi_depths(self) -> list[int]: """Return a list of MZI depth, per mode""" return self._depth_per_mode def remove_phase_layer(self): """Remove the optional phase layer at the input or at the output. Does nothing if such a layer does not exist""" if self._has_input_phase_layer: self._components = self._components[self.m:] if self._has_output_phase_layer: self._components = self._components[:-self.m] def set_identity_mode(self): """Set the interferometer in identity mode (i.e. photons are output on the mode they're input)""" for p in self.get_parameters(): p.set_value(math.pi) def _add_single_mode_component(self, mode: int, component: ACircuit) -> None: """Add a component to the circuit, check if it's a one mode circuit :param mode: mode to add the component :param component: component to add """ assert component.m == 1, f"Component should always be a one mode circuit, instead it's a {component.m} modes circuit" self.add(mode, component) def _add_upper_component(self, i_depth: int) -> None: """Add a component with upper_component_gen between the interferometer on the first mode :param i_depth: depth index of the interferometer """ if self._upper_component_gen and i_depth % 2 == 1: self._add_single_mode_component(0, self._upper_component_gen(i_depth // 2)) def _add_lower_component(self, i_depth: int) -> None: """Add a component with lower_component_gen between the interferometer on the last mode :param i_depth: depth index of the interferometer """ # If m is even, the component is added at even depth index, else it's added in at odd depth index if (self._lower_component_gen and ((i_depth % 2 == 1 and self.m % 2 == 0) or (i_depth % 2 == 0 and self.m % 2 == 1))): self._add_single_mode_component(self.m - 1, self._lower_component_gen(i_depth // 2)) def _build_rectangle(self): max_depth = self.m if self._depth is None else self._depth idx = 0 for i in range(0, max_depth): self._add_upper_component(i) for j in range(0+i%2, self.m-1, 2): if self._depth is not None and (self._depth_per_mode[j] == self._depth or self._depth_per_mode[j+1] == self._depth): continue self.add((j, j+1), self._pattern_generator(idx), merge=True) self._depth_per_mode[j] += 1 self._depth_per_mode[j+1] += 1 idx += 1 self._add_lower_component(i) if self._use_barriers: self.add(0, Barrier(self.m, visible=False)) def _build_triangle(self): idx = 0 for i in range(1, self.m): for j in range(i, 0, -1): if self._depth is not None and (self._depth_per_mode[j-1] == self._depth or self._depth_per_mode[j] == self._depth): continue self.add((j-1, j), self._pattern_generator(idx), merge=True) if self._use_barriers: self.add((j-1, j), Barrier(2, visible=False)) self._depth_per_mode[j-1] += 1 self._depth_per_mode[j] += 1 idx += 1 def _find_param_index(self, col: int, lin: int, even_col_size: int, odd_col_size: int) -> int: p_idx = (even_col_size + odd_col_size) * (col // 2) if col % 2 == 1: p_idx += even_col_size p_idx += lin * 2 if self._has_input_phase_layer: p_idx += self.m return p_idx @staticmethod def _compute_insertion_depth(start_col: int, m: int, param_count: int) -> int: depth = 0 cc = 0 k = 0 while k < param_count: depth += 1 k += 2*(m//2) if (start_col+cc)%2 == 0 else 2*((m-1)//2) cc += 1 return depth def set_param_list(self, param_list: list[float], top_left_pos: tuple[int, int], m: int): """Insert parameters value starting from a given position in the interferometer. This method is designed to work on rectangular interferometers :param param_list: List of numerical values for the parameters :param top_left_pos: Starting position of the insertion (column#, row#). Position is handled MZI-wise (i.e. (0,0) starts inserting values on the top-left-most MZI of the interferometer whereas (1,0) starts on top of the 2nd MZI column). The optional phase layer is ignored in the position handling. :param m: Mode count on where to insert the parameter values """ col, lin = top_left_pos depth = self._compute_insertion_depth(col, m, len(param_list)) if col < 0 or col+depth-1 >= self.m: raise ValueError(f"Invalid param list width, expected interval in [0,{self.m}], got [{col},{col+depth-1}]") if lin < 0 or lin+m >= self.m: raise ValueError(f"Invalid param list height, expected interval in [0,{self.m}], got [{lin},{lin+m}]") if self._shape != InterferometerShape.RECTANGLE: get_logger().warn("set_param_list was designed for rectangular interferometer", channel.user) even_mode_count = self.m % 2 == 0 even_col_size = self.m - (self.m % 2) odd_col_size = even_col_size - 2 if even_mode_count else even_col_size self_params = self.get_parameters() cc = 0 # current col k = 0 while k < len(param_list): start = self._find_param_index(col+cc, lin, even_col_size, odd_col_size) param_count = 2*(m//2) if (col+cc) % 2 == 0 else 2*((m-1)//2) for j in range(param_count): self_params[start+j].set_value(param_list[k]) k += 1 if k == len(param_list): break cc += 1 def set_params_from_other(self, other: Circuit, top_left_pos: tuple[int, int]): """Retrieve parameter value from another interferometer :param other: Another interferometer :param top_left_pos: Starting position of the insertion. See full description in `set_param_list` """ if not other.defined: raise ValueError("Cannot copy parameters from a circuit which isn't fully defined") param_list = [float(p) for p in other.get_parameters()] self.set_param_list(param_list, top_left_pos, other.m) ================================================ FILE: perceval/components/linear_circuit.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing import copy import random from abc import ABC, abstractmethod from collections.abc import Callable from typing import Generator import numpy as np import sympy as sp import scipy.optimize as so from perceval.components.abstract_component import AParametrizedComponent from perceval.utils import Parameter, Matrix, MatrixN, matrix_double, global_params, InterferometerShape from perceval.utils.logging import get_logger, channel from perceval.utils.algorithms import decomposition, Match from perceval.utils.algorithms.solve import solve class ACircuit(AParametrizedComponent, ABC): """ Abstract linear optics circuit class. A circuit is defined by a dimension `m`, and by parameters. Parameters can be fixed (value) or variables. """ _supports_polarization = False def __init__(self, m: int, name: str = None): super().__init__(m, name) @abstractmethod def _compute_unitary(self, assign: dict = None, use_symbolic: bool = False) -> Matrix: """Compute the unitary matrix corresponding to the current circuit :param assign: assign values to some parameters :param use_symbolic: if the matrix should use symbolic calculation :return: the unitary matrix, will be a :class:`~perceval.utils.matrix.MatrixS` if symbolic, or a ~`MatrixN` if not. """ def compute_unitary(self, assign: dict = None, use_symbolic: bool = False, use_polarization: bool | None = None) -> Matrix: """Compute the unitary matrix corresponding to the current circuit :param use_polarization: ask for polarized circuit to double size unitary matrix :param assign: optional mapping between parameter names and their corresponding values :param use_symbolic: if the matrix should use symbolic calculation :return: the unitary matrix, will be a :class:`~perceval.utils.matrix.MatrixS` if symbolic, or a ~`MatrixN` if not. """ if not use_symbolic: assert self.defined, 'All parameters must be defined to compute numeric unitary matrix' if self._supports_polarization: assert use_polarization is not False, "polarized circuit cannot generates non-polarized unitary" use_polarization = True elif use_polarization is None: use_polarization = False u = self._compute_unitary(assign, use_symbolic) if use_polarization and not self._supports_polarization: return matrix_double(u) return u @property def requires_polarization(self): """Does the circuit require polarization? :return: is True if the circuit has a polarization component """ return self._supports_polarization @property def U(self): """ get the symbolic unitary matrix """ return self.compute_unitary(use_symbolic=True).simp() def definition(self) -> Matrix: r"""Gives mathematical definition of the circuit Only defined for elementary circuits """ params = {name: Parameter(name) for name in self._params.keys()} return type(self)(**params).U def add(self, port_range: int | tuple[int, ...], component: ACircuit, merge: bool = None) -> Circuit: return Circuit(self._m).add(0, self).add(port_range, component, merge) def __setitem__(self, key, value): self._params[key] = value def __ifloordiv__(self, component: ACircuit | tuple[int, ACircuit]) -> Circuit: r"""Shortcut for ``.add`` >>> c //= b # equivalent to: `c.add((0:b.m),b)` >>> c //= (i, b) # equivalent to: `c.add((i:i+b.m), b)` :param component: the component to add, or a tuple (first_port, component) """ if isinstance(component, tuple): assert len(component) == 2 and isinstance(component[0], int), "invalid //(i,C) operation" pos = component[0] component = component[1] else: pos = 0 return self.add(tuple(range(pos, component._m+pos)), component, merge=True) def __floordiv__(self, component: ACircuit | tuple[int, ACircuit]) -> Circuit: r"""Build a new circuit by adding `component` to the current circuit >>> c = a // b # equivalent to: `Circuit(n) // self // component` :param component: the component to add, or a tuple (first_port, component) """ c = copy.copy(self) c //= component return c def __iter__(self): """ Iterator on a circuit, recursively returns modes and components in the order they were added to the circuit. Flattens the circuit if there are sub-circuits. :return: generator of tuples (r, c) where r is the tuple containing the modes of the component in ascending order, and c is the component itself. """ yield tuple(pos for pos in range(self._m)), self def identify(self, unitary_matrix, phases, precision=None, max_try=10, allow_error=False) -> None: r"""Identify an instance of the current circuit (should be parameterized) such as :math:`Q.C=U.P` where :math:`Q` and :math:`P` are single-mode phase shifts (resp. :math:`[q1, q2, ..., qn]`, and :math:`[p1, p2, ...,pn]`). This is solved through :math:`n^2` equations: :math:`q_i * C_{i,j}(x,y, ...) = UP_{i,j} * p_j` :param unitary_matrix: the matrix to identify :param phases: phase shift parameters :param max_try: the resolution is using parameter search starting on a random points, it might fail, this parameter sets the maximal number of times to try """ if precision is None: precision = global_params["min_complex_component"] params = [x.spv for x in self.get_parameters()] Q = Matrix.eye(self._m, use_symbolic=True) P = Matrix.eye(self._m, use_symbolic=False) for i in range(self._m): params.append(sp.S("q%d" % i)) Q[i, i] = sp.exp(1j*params[-1]) P[i, i] = phases[i] cU = Q @ self.compute_unitary(use_symbolic=True) UP = unitary_matrix @ P equation = None for i in range(self._m): for j in range(self._m): if equation is None: equation = abs(cU[i, j]-UP[i, j]) else: equation += abs(cU[i, j]-UP[i, j]) equation = abs(equation) f = sp.lambdify([params], equation, modules=np) counter = 0 while counter < max_try: x0 = [random.random()] * len(params) res = so.minimize(f, x0, method="L-BFGS-B") if res.fun <= precision or allow_error: return res.x[:len(self.get_parameters())], res.x[-self._m:] counter += 1 return None @staticmethod def _match_unitary(circuit: ACircuit | Matrix, pattern: ACircuit, match: Match = None, actual_pos: int | None = 0, actual_pattern_pos: int | None = 0) -> Match | None: r"""match an elementary component by finding if possible the corresponding parameters. :param pattern: the circuit to match :param pattern: the circuit to match against :param match: current partial match :param actual_pos: the actual position of the component in the circuit :param actual_pattern_pos: the actual position of the component in the pattern :return: resulting parameter/value constraint if there is a match or None otherwise """ if match is None: match = Match() if isinstance(circuit, ACircuit): u = circuit.compute_unitary(use_symbolic=False) else: u = circuit # unitaries should match - check the variables params_symbols = [] params_values = [] x0 = [] bounds = [] for p in pattern.get_parameters(): params_symbols.append(p.spv) params_values.append(match.v_map.get(p.name, None)) if not p.is_periodic: bounds.append((p.min, p.max)) else: bounds.append(None) x0.append(p.random()) cu = pattern.compute_unitary(use_symbolic=True) f = sp.lambdify([params_symbols], cu - u, modules=np) def g(*params): return np.linalg.norm(np.array(f(*params))) res = solve(g, x0, params_values, bounds, precision=global_params["min_complex_component"]) if res is not None: n_match = copy.deepcopy(match) for p, v in zip(pattern.get_parameters(), res): n_match.v_map[p.name] = p.check_value(v) n_match.pos_map[actual_pos] = actual_pattern_pos return n_match return None def match(self, pattern: ACircuit, pos: int = None, pattern_pos: int = None, match: Match = None, actual_pos = 0, actual_pattern_pos=0) -> Match | None: # the component shape should match if pattern.name == "CPLX" or self._m != pattern._m or pos is not None or pattern_pos is not None: return None return ACircuit._match_unitary(self, pattern, match, actual_pos=actual_pos, actual_pattern_pos=actual_pattern_pos) def transfer_from(self, source: ACircuit, force: bool = False): r"""Transfer parameters of a circuit to the current one :param source: the circuit to transfer the parameters from. The shape of the circuit to transfer from should be a subset of the current circuit. :param force: force changing fixed parameter if necessary """ assert type(self) == type(source), "component does not have the same shape" for p in source.params: assert p in self._params, "missing parameter %s when transferring component" % p param = source.param(p) if param.defined: try: self._params[p].set_value(float(param), force=force) except RuntimeError: # Error in case force = False and param is fixed if abs(float(param) - float(self._params[p])) >= global_params["min_complex_component"]: raise ValueError(f"components don't have the same fixed value for parameter {p}") except Exception as e: get_logger().error(f"Unexpected error in tranfer_from: {e}", channel.general) def depths(self): """ :return: the depth of the circuit for each mode """ return [1]*self.m def ncomponents(self): """ :return: number of actual components in the circuit """ return 1 def inverse(self, v, h): raise NotImplementedError("component has no inverse operator") @abstractmethod def describe(self) -> str: """ Describe the component as the Python code that generates it. :return: code generating the component """ pass class Circuit(ACircuit): """Class to represent any circuit composed of one or multiple components :param m: The number of port of the circuit :param name: Name of the circuit """ DEFAULT_NAME = "CPLX" _color = None # A circuit can be given a background color when displayed as a subcircuit def __init__(self, m: int, name: str = None): assert m > 0, "invalid size" super().__init__(m, name) self._components = [] def is_composite(self): return True def __iter__(self) -> Generator[tuple[tuple[int, ...], ACircuit]]: for r, c in self._components: for range_comp, comp in c: yield tuple(pos + r[0] for pos in range_comp), comp def getitem(self, idx: tuple[int, int], only_parameterized: bool=False) -> ACircuit: """ Direct access to components of the circuit :param idx: index of the component as (row, col) :param only_parameterized: if True, only count components with parameters :return: the component """ if not(isinstance(idx, tuple) and len(idx) == 2): raise ValueError("__getitem__ type should be len-2 tuple") # get j-th component found on mode i i, j = idx if i >= self._m or i < 0: raise IndexError("row index out of range") for r, c in self._components: if only_parameterized and c.defined: continue if hasattr(c, "visible") and not c.visible: continue if i in r: if j == 0: return c j -= 1 raise IndexError("column index out of range") def get_parameters(self, all_params: bool = False, expressions = False) -> list[Parameter]: """Return the parameters of the circuit :param all_params: if False, only returns the variable parameters :expressions: if True, returns highest level Expressions and parameters only. If False, returns the raw parameters that make up the expressions only. Default `False`. :return: the list of parameters """ total_params = dict() for _, comp in self: if comp._params: comp_params = comp.get_parameters(all_params, expressions) total_params.update(dict.fromkeys(comp_params)) return list(total_params.keys()) def __getitem__(self, idx) -> ACircuit: """ Direct access to components - using __getitem__ operator :param idx: index of the component as (row, col) :return: the component """ return self.getitem(idx, only_parameterized=False) def describe(self) -> str: r"""Describe a circuit :return: a string describing the circuit that be re-used to define the circuit """ cparams = [f"{self._m}"] if self.name != Circuit.DEFAULT_NAME: cparams.append(f"name='{self._name}'") desc = f"Circuit({', '.join(cparams)})" for r, c in self._components: if len(r) == 1: r = r[0] desc += f".add({r}, {c.describe()})" return desc @property def requires_polarization(self) -> bool: return any(c.requires_polarization for _, c in self._components) def definition(self) -> Matrix: raise RuntimeError("`definition` method is only available on elementary circuits") def barrier(self): r"""Add a barrier to a circuit The barrier is a visual marker to break down a circuit into sections. Behind the scenes, it is implemented as a Barrier unitary operating on all modes. At the moment, the barrier behaves exactly like a component with a unitary equal to identity. """ # Hack to prevent circular definitions from perceval.components.unitary_components import Barrier return self.add(0, Barrier(self._m)) def __imatmul__(self, component: ACircuit | tuple[int, ACircuit]) -> Circuit: r"""Add a barrier and a `component` to the current circuit :param component: the component to add, or a tuple (first_port, component) """ self.barrier() self //= component return self def __matmul__(self, component: ACircuit | tuple[int, ACircuit]) -> Circuit: r"""Build a new circuit by adding a barrier and then `component` to the current circuit :param component: the component to add, or a tuple (first_port, component) """ c = copy.copy(self) c @= component return c def add(self, port_range: int | tuple[int, ...], component: ACircuit, merge: bool = False) -> Circuit: r"""Add a component in a circuit :param port_range: the port range as a tuple of consecutive ports, or the initial port where to add the component :param component: the component to add, must be a linear component or circuit :param merge: when the component is a complex circuit, if True, flatten the added circuit. Otherwise, keep the nested structure (default False) :return: the circuit itself, allowing to add multiple components in a same line :raise: ``AssertionError`` if parameters are not valid """ assert isinstance(component, ACircuit), \ "Only unitary components can compose a linear optics circuit, use Processor for non-unitary" if isinstance(port_range, int): port_range = tuple([i for i in range(port_range, port_range+component.m)]) if isinstance(port_range, list): port_range = tuple(port_range) assert isinstance(port_range, tuple), f"Range ({port_range}) must be a tuple" for i, x in enumerate(port_range): assert isinstance(x, int) and i == 0 or x == port_range[i - 1] + 1, \ "Range must be a consecutive set of port indexes" assert min(port_range) >= 0 and max(port_range) < self.m, \ f"Port range exceeds circuit size (received {port_range} but maximum expected value is {self.m-1})" assert len(port_range) == component.m, \ f"Port range ({len(port_range)}) is not matching component size ({component.m})" # merge the parameters - we are only interested in non-assigned parameters if it is not a global operator for _, p in component._params.items(): if not p.fixed: for internal_p in p._params: if internal_p.name in self._params and internal_p is not self._params[internal_p.name]: raise RuntimeError(f"two parameters with the same name in the circuit {internal_p.name}") self._params[internal_p.name] = internal_p # register the component if merge and isinstance(component, Circuit) and component._components: for sprange, sc in component._components: nprange = tuple(r + port_range[0] for r in sprange) self._components.append((nprange, sc)) else: self._components.append((port_range, component)) return self def _compute_unitary(self, assign: dict = None, use_symbolic: bool = False) -> Matrix: pass def _compute_circuit_unitary(self, use_symbolic: bool, use_polarization: bool) -> Matrix: """compute the unitary matrix corresponding to the current circuit""" u = None multiplier = 2 if use_polarization else 1 for r, c in self._components: cU = c.compute_unitary(use_symbolic=use_symbolic, use_polarization=use_polarization) if len(r) != multiplier*self._m: nU = Matrix.eye(multiplier*self._m, use_symbolic) nU[multiplier*r[0]:multiplier*(r[-1]+1), multiplier*r[0]:multiplier*(r[-1]+1)] = cU cU = nU if u is None: u = cU else: u = cU @ u return u def inverse(self, v=False, h=False): """ Inverts a circuit in place, depending on the values of ``h`` and ``v``. :param h: Stands for horizontal. If True, the circuit will be reversed such that its final unitary matrix is the inverse of the original one. :param v: Stands for vertical. If True, the circuit will be reversed such that the mode 0 becomes the mode :math:`m-1`, the mode 1 becomes the mode :math:`m - 2`... """ _new_components = [] _components = self._components if h: _components.reverse() for rc in _components: range, component = rc if v: if isinstance(range, int): range = [range] else: range = list(range) range.reverse() range = [self._m - 1 - p for p in range] if v or h: component.inverse(v=v, h=h) for param in component.get_parameters(expressions=True): self._params[param.name] = param _new_components.append((range, component)) self._components = _new_components def compute_unitary(self, assign: dict = None, use_symbolic: bool = False, use_polarization: bool = None) -> Matrix: self.assign(assign) if use_polarization is None: use_polarization = self.requires_polarization elif not use_polarization: assert self.requires_polarization is False, "polarized circuit cannot generates non-polarized unitary" u = self._compute_circuit_unitary(use_symbolic, use_polarization) if u is None: u = Matrix.eye(self._m, use_symbolic=use_symbolic) return u def copy(self): """Return a deep copy of the current circuit""" return copy.deepcopy(self) @staticmethod def decomposition(U: MatrixN, component: ACircuit, phase_shifter_fn: Callable[[int], ACircuit] = None, shape: str | InterferometerShape = InterferometerShape.TRIANGLE, permutation: type[ACircuit] = None, inverse_v: bool = False, inverse_h: bool = False, constraints=None, merge: bool = True, precision: float = 1e-6, max_try: int = 10, allow_error: bool = False, ignore_identity_block: bool = True): r"""Decompose a given unitary matrix U into a circuit with a specified component type :param U: the matrix to decompose :param component: a circuit, to solve any decomposition must have up to 2 independent parameters :param phase_shifter_fn: a function generating a phase_shifter circuit. If `None`, residual phase will be ignored :param shape: shape of the decomposition (`triangle` is natively supported in Perceval) :param permutation: if provided, type of permutation operator to avoid unnecessary operators :param inverse_v: inverse the decomposition vertically :param inverse_h: inverse the decomposition horizontally :param constraints: constraints to apply on both parameters, it is a list of individual constraints. Each constraint should have the numbers of free parameters of the system. :param merge: don't use sub-circuits :param precision: for intermediate values - norm below precision are considered 0. If not - use `global_params` :param max_try: number of times to try the decomposition :param allow_error: allow decomposition error - when the actual solution is not locally reachable :param ignore_identity_block: If true, do not insert a component when it's not needed (component is an identity) Otherwise, insert a component everytime (default True). :return: a circuit """ if isinstance(shape, str): try: shape = InterferometerShape[shape.upper()] except: raise ValueError(f"Unknown interferometer shape: {shape}") if not Matrix(U).is_unitary() or Matrix(U).is_symbolic(): raise(ValueError("decomposed matrix should be non symbolic unitary")) if inverse_h: U = U.inv() if inverse_v: U = np.flip(U) N = U.shape[0] count = 0 if constraints is not None: assert isinstance(constraints, list), "constraints should be a list of constraint" for constraint in constraints: assert isinstance(constraint, (list, tuple)) and len(constraint) == len(component.get_parameters()),\ "there should as many component in each constraint than free parameters in the component" while count < max_try: if shape == InterferometerShape.TRIANGLE: lc = decomposition.decompose_triangle(U, component, phase_shifter_fn, permutation, precision, constraints, allow_error=allow_error, ignore_identity_block=ignore_identity_block) elif shape == InterferometerShape.RECTANGLE: lc = decomposition.decompose_rectangle(U, component, phase_shifter_fn, permutation, precision, constraints, allow_error=allow_error, ignore_identity_block=ignore_identity_block) else: raise NotImplementedError(f"Shape {shape} not supported") if lc is not None: C = Circuit(N) for range, component in lc: C.add(range, component, merge=merge) if inverse_v or inverse_h: C.inverse(v=inverse_v, h=inverse_h) return C count += 1 return None def depths(self): the_depths = [0] * self.m for r, c in self._components: c_depths = c.depths() for c_i, i in enumerate(r): the_depths[i] += c_depths[c_i] return the_depths def ncomponents(self): n = 0 for _, c in self._components: n += c.ncomponents() return n def transfer_from(self, source: ACircuit, force: bool = False): assert source.m == self.m, "circuit shape does not match" checked_components = [False] * len(self._components) for r, c in source._components: # find the component c in the current circuit, we can only take a component at the border # of the explored components for idx, (r_self, c_self) in enumerate(self._components): if checked_components[idx]: continue if r_self == r: c_self.transfer_from(c, force) checked_components[idx] = True break else: assert r_self[-1] < r[0] or r_self[0] > r[-1], \ "circuit structure does not match - missing %s at %s" % (str(c), str(r)) def find_subnodes(self, pos: int) -> list[int]: r"""find the subnodes of a given component (Udef for pos==None) :param pos: the position of the current node :return: """ if pos is None: r = [0, self._m-1] else: r = [self._components[pos][0][0], self._components[pos][0][-1]] subnodes = [] for i in range(r[0], r[1]+1): found = False for p in range(pos + 1, len(self._components)): try: idx = self._components[p][0].index(i) found = True break except ValueError: pass subnodes.append(found and (p, idx) or None) return subnodes def isolate(self, lc: list[int], name=None, color=None): nlc = [] rset = set() for idx in lc: r, _ = self._components[idx] for ir in r: rset.add(ir) sub_r = sorted(rset) sub_circuit = Circuit(len(sub_r), name=name is None and "pattern" or name) if color is not None: sub_circuit._color = color for idx in sorted(lc): r, c = self._components[idx] sub_circuit.add(r[0]-sub_r[0], c) pidx = None for idx, (r, c) in enumerate(self._components): if idx in lc: if idx == lc[-1]: pidx = len(nlc) nlc.append((sub_r, sub_circuit)) else: nlc.append((r, c)) self._components = nlc return pidx def replace(self, p: int, pattern: ACircuit, merge: bool = False): nlc = [] for idx, (r, c) in enumerate(self._components): if idx == p: if isinstance(pattern, Circuit) and merge: for r1, c1 in pattern._components: nlc.append(([pr1+r[0] for pr1 in r1], c1)) else: nlc.append(([idx+r[0] for idx in range(pattern._m)], pattern)) else: nlc.append((r, c)) self._components = nlc def _check_brother_node(self, p0, p1): r"""check that component at p0 is a brother node than component at p1 - p0 < p1 """ for p in range(p0, p1): for qr in self._components[p][0]: if qr in self._components[p1][0]: return False return True def match(self, pattern: ACircuit, pos: int = None, pattern_pos: int = 0, browse: bool = False, match: Match = None, actual_pos: int = None, actual_pattern_pos: int = None, reverse: bool = False) -> Match | None: r"""match a sub-circuit at a given position :param match: the partial match :param browse: true if we want to search the pattern at any location in the current circuit, if true, pos should be None :param pattern: the pattern to search for :param pos: the start position in the current circuit :param pattern_pos: the start position in the pattern :param actual_pos: unused, parameter only used by parent class :param actual_pattern_pos: unused, parameter only used by parent class :param reverse: true if we want to search the pattern from the end of the circuit to pos (or the 0 if browse) :return: """ assert actual_pos is None and actual_pattern_pos is None, "invalid use of actual_*_pos parameters for Circuit" if browse: if pos is None: pos = 0 l = list(range(pos, len(self._components))) if reverse: l.reverse() for pos in l: match = self.match(pattern, pos, pattern_pos) if match is not None: return match return None # first to match - we need to have a match on the component itself - self[pos] == circuit[pattern_pos] if match is None: match = Match() else: # if we have already matched the component, the matchee and the matcher should be the same ! if pos in match.pos_map and match.pos_map[pos] != pattern_pos: return None if not isinstance(pattern, Circuit): # the circuit we have to match against has a single component return self._components[pos][1].match(pattern, match, actual_pos=pos, actual_pattern_pos=pattern_pos) # the circuit we have to match against has multiple components if pos is None: pos = 0 match = self._components[pos][1].match(pattern._components[pattern_pos][1], match=match, actual_pos=pos, actual_pattern_pos=pattern_pos) if match is None: return None # if actual_pattern_pos is 0, we also have to match potential brother nodes if pattern_pos == 0: map_modes = set() pattern_brother_nodes = {} for qr in pattern._components[pattern_pos][0]: map_modes.add(qr) for qc in range(1, len(pattern._components)): # either they are a sub-nodes r, _ = pattern._components[qc] overlap = False for qr in r: if qr in map_modes: overlap = True break if not overlap: pattern_brother_nodes[r[0] - pattern._components[pattern_pos][0][0]] = qc for qr in r: map_modes.add(qr) if len(map_modes) == pattern._m: break for r_bn, p_bn in pattern_brother_nodes.items(): # looking for a similar component starting on relative mode r_bn found_bn = False c_bn = pattern._components[p_bn][1] for qc in range(pos-1, -1, -1): r, c = self._components[qc] r0 = r[0] - self._components[pos][0][0] if r0 == r_bn and c.m == c_bn.m: found_bn = self._check_brother_node(qc, pos) break if not found_bn: for qc in range(pos+1, len(self._components)): r, c = self._components[qc] r0 = r[0] - self._components[pos][0][0] if r0 == r_bn and c.m == c_bn.m: found_bn = self._check_brother_node(pos, qc) break if not found_bn: return None match = self.match(pattern, qc, p_bn, False, match) if match is None: return None # now iterate through all subnodes of circuit[pos] - they should match equivalent sub nodes of self[pos] circuit_sub_nodes = pattern.find_subnodes(pattern_pos) self_sub_nodes = self.find_subnodes(pos) for c_self, c_circuit in zip(self_sub_nodes, circuit_sub_nodes): if c_circuit is None: continue if c_self is None: return None if c_self[1] != c_circuit[1]: return None match = self.match(pattern, c_self[0], c_circuit[0], False, match) if match is None: return None return match ================================================ FILE: perceval/components/non_unitary_components.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np import scipy as sp from perceval.utils import BasicState, StateVector, Parameter from perceval.components.abstract_component import AParametrizedComponent class TD(AParametrizedComponent): """ Time Delay is a special component corresponding to a roll of optical fiber making as an effect to delay a photon. Parameter of the Time Delay is the number of period the delay should be. For instance ``TD(2)`` will create a delay on the mode corresponding to two periods. A time delay is not expressed as a unitary matrix and can only be used in processors. :param dt: Number of periods """ DEFAULT_NAME = "TD" def __init__(self, dt: int | Parameter): super().__init__(1) self._dt = self._set_parameter("t", dt, 0, None, False) def get_variables(self): parameters = {} self._populate_parameters(parameters, "t") return parameters def describe(self): if self._dt.fixed: value = float(self._dt) else: value = f'P("{self._dt.spv}")' return f"TD(t={value})" class LC(AParametrizedComponent): """ Loss channels are non-unitary components applying a fixed loss on a given mode. A loss channel is equivalent to a beam splitter with a reflectivity equal to the loss, being connected to a virtual mode containing lost photons. A loss channel is not expressed as a unitary matrix and can only be used in processors. :param loss: Loss rate expressed as a floating point number between 0 and 1. """ DEFAULT_NAME = "LC" def __init__(self, loss: float | Parameter): super().__init__(1) self._loss = self._set_parameter("loss", loss, 0, 1, False) def get_variables(self): out = {} self._populate_parameters(out, "loss") return out def describe(self): if self._loss.fixed: value = float(self._loss) else: value = f'P("{self._loss.spv}")' return f"LC(loss={value})" def apply(self, r, sv): """ Applies a channel loss to r-th mode on an input StateVector sv Channel loss is treated as a beam splitter with a reflectivity equal to the loss. This beam splitter being connected to a "virtual" mode containing lost photons The output state vector contains BasicStates which are 1 mode bigger than the ones in input: (input modes count + the virtual mode) Warning: this method loses annotations! However, it is not currently used in LC simulation (see: LossSimulator) """ # Assumes r of size 1 # Returns a stateVector of size m + 1. Stepper backend should support this if isinstance(sv, BasicState): sv = StateVector(sv) r = r[0] loss = self.get_variables()["loss"] n_max = max(state[r] for state in sv) N = np.arange(n_max + 1) k = np.arange(n_max + 1) k = np.tile(k, (n_max+1, 1)).transpose() prob = sp.special.comb(np.tile(N, (n_max+1, 1)), k) prob *= loss ** (sp.sparse.diags([(n_max + 1 - i) * [i] for i in range(n_max + 1)], list(range(n_max + 1))).toarray()) prob *= (1 - loss) ** k prob = np.sqrt(prob) nsv = StateVector() nsv.m = sv.m + 1 # Equivalent to the nsv.update below: # for state, prob_ampli in sv.items(): # n = state[r] # for i in range(n + 1): # nsv[BasicState(state.set_slice(slice(r, r+1), BasicState([i]))) * BasicState([n - i])] += prob_ampli \ # * (loss ** (n - i) # * (1 - loss) ** i # * comb(n, i)) ** 0.5 # Dict comprehension is possible here as two different basic states can't give the same resulting state nsv.update( { BasicState(state.set_slice(slice(r, r + 1), BasicState([i]))) * BasicState([state[r] - i]): prob_ampli * prob[i, state[r]] for state, prob_ampli in sv.items() for i in range(state[r] + 1) } ) return nsv ================================================ FILE: perceval/components/port.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import ABC, abstractmethod from enum import Enum from perceval.utils import BasicState, Encoding, LogicalState, ModeType from .abstract_component import AComponent class PortLocation(Enum): """Port positioning, at the beginning, the end of a given `Experiment`.""" INPUT = 0 OUTPUT = 1 IN_OUT = 2 PortLocation.INPUT.__doc__ = "Port is only at the input of the `Experiment`" PortLocation.OUTPUT.__doc__ = "Port is only at the output of the `Experiment`" PortLocation.IN_OUT.__doc__ = "Ports are often symmetrical, this value puts them in both locations." class APort(AComponent, ABC): def __init__(self, size, name): super().__init__(size, name) @staticmethod def supports_location(loc: PortLocation) -> bool: return True @abstractmethod def output_mode_type(self) -> ModeType: """ Return the mode type induced by the port """ @property def encoding(self) -> Encoding | None: """The port encoding""" return None class Port(APort): """ A port attached to one or more modes :param encoding: The corresponding qubit encoding :param name: The port's name """ def __init__(self, encoding: Encoding, name: str): super().__init__(encoding.fock_length, name) self._encoding = encoding @property def encoding(self): return self._encoding def output_mode_type(self) -> ModeType: return ModeType.PHOTONIC class Herald(APort): """ A fixed input or an herald (output) attached to a single mode. :param value: Expected input photon / output detection :param name: Optional herald name. Can be fixed or autogenerated as 'herald{int}' """ def __init__(self, value: int, name: str | int = None): self._autogenerated_name = isinstance(name, int) if self._autogenerated_name: name = f'herald{name}' super().__init__(1, name) self._value = value self.detector_type = None def output_mode_type(self) -> ModeType: return ModeType.HERALD @property def user_given_name(self): """:return: the herald name if it was fixed by the user. None otherwise""" if self._autogenerated_name: return None return self._name @property def expected(self): """:return: the expected output photon / output detection""" return self._value def get_basic_state_from_ports(ports: list[APort], state: LogicalState, add_herald_and_ancillary: bool = False) -> BasicState: """Convert a LogicalState to a BasicState by taking in account a port list :param ports: port list. :param state: the logical state to convert :param add_herald_and_ancillary: add the herald and ancillary port to the basic state. Default to False. :return: the converted Fock state """ encodings = [] for port in ports: if isinstance(port, Herald): if add_herald_and_ancillary: encodings.append(port.expected) else: encodings.append(port.encoding) return get_basic_state_from_encoding(encodings, state) def _to_fock(encoding: Encoding, qubit_state: list[int]) -> list[int]: """Return the equivalent BasicState from the qubit state, as a list of integers :param encoding: a qubit encoding :param qubit_state: logical state for the required number of qubits (only 0 or 1 values are accepted) :raises NotImplementedError: QUBIT and POLARIZATION encoding not currently supported :return: The corresponding Fock state """ if encoding.logical_length != len(qubit_state): raise ValueError("Encoding / logical state size mismatch") if any(q not in [0, 1] for q in qubit_state): raise ValueError("Qubit value should be 0 or 1") if encoding == Encoding.RAW: return [int(qubit_state[0])] elif encoding == Encoding.DUAL_RAIL: return [0, 1] if qubit_state[0] else [1, 0] elif encoding.name.startswith("QUDIT"): fock = [0]*encoding.fock_length photon_pos = sum(val*(2**idx) for idx, val in enumerate(reversed(qubit_state))) fock[photon_pos] = 1 return fock else: raise NotImplementedError def get_basic_state_from_encoding(encoding: list[Encoding | int], logical: LogicalState) -> BasicState: """ Convert a LogicalState to a BasicState from an encoding list. :param encoding: encoding list :param logical: the logical state to convert :raises TypeError: if encoding contains unsupported types :raises ValueError: if the encoding and logical state sizes do not match :return: the converted Fock state """ fock = [] i = 0 for e in encoding: if isinstance(e, int): fock.append(e) elif isinstance(e, Encoding): lsz = e.logical_length ls = logical[i:i+lsz] i += lsz fock += _to_fock(e, ls) else: raise TypeError(f"Unsupported type {type(e)}") if i != len(logical): raise ValueError("Encoding / logical state size mismatch") return BasicState(fock) ================================================ FILE: perceval/components/processor.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sys from perceval.utils import SVDistribution, BasicState, FockState, AnnotatedFockState, StateVector, NoiseModel from perceval.utils.logging import get_logger, channel from .abstract_processor import AProcessor, ProcessorType from .experiment import Experiment from .linear_circuit import ACircuit, Circuit from .source import Source class Processor(AProcessor): """ Generic definition of processor as an experiment + simulation backend :param backend: Name or instance of a simulation backend :param m_circuit: can either be: * an int: number of modes of interest (MOI). A mode of interest is any non-heralded mode. >>> p = Processor("SLOS", 5) * a circuit: the input circuit to start with. Other components can still be added afterwards with `add()` >>> p = Processor("SLOS", BS() // PS() // BS()) * an experiment: >>> p = Processor("SLOS", Experiment(BS(), NoiseModel(0.8))) :param noise: a NoiseModel containing noise parameters (defaults to no noise) :param name: a textual name for the processor (defaults to "Local processor") """ def __init__(self, backend, m_circuit: int | ACircuit | Experiment = None, noise: NoiseModel = None, name: str = "Local processor"): if not isinstance(m_circuit, Experiment): m_circuit = Experiment(m_circuit, noise=noise, name=name) elif noise: m_circuit = m_circuit.copy() # Create a copy so that we don't change the input experiment m_circuit.noise = noise super().__init__(m_circuit) self._init_backend(backend) self._inputs_map = None self._noise_changed_observer() self._input_changed_observer() self._simulator = None self._compute_physical_logical_perf = False @property def _has_custom_input(self): return (isinstance(self.input_state, SVDistribution) or (isinstance(self.input_state, AnnotatedFockState) and self.input_state.has_polarization)) def _noise_changed_observer(self): self._source = Source.from_noise_model(self.noise) if not self._has_custom_input: self._inputs_map = None @AProcessor.noise.getter def noise(self): noise = super(Processor, type(self)).noise.fget(self) if noise is None: return NoiseModel() return noise @property def source_distribution(self) -> SVDistribution | None: r""" Retrieve the computed input distribution. Compute it if it is not cached and an input state has been provided. :return: the input SVDistribution if `with_input` was called previously, otherwise None. """ if self._inputs_map is None and self.input_state is not None: self._generate_noisy_input() return self._inputs_map def _circuit_change_observer(self, new_component = None): self._simulator = None @property def source(self): """ :return: The photonic source """ return self._source def _init_backend(self, backend): if isinstance(backend, str): from perceval import BACKEND_LIST assert backend in BACKEND_LIST, f"Simulation backend '{backend}' does not exist" self.backend = BACKEND_LIST[backend]() else: from perceval import ABackend assert isinstance(backend, ABackend), f"'backend' must be an ABackend (got {type(backend)})" self.backend = backend def type(self) -> ProcessorType: return ProcessorType.SIMULATOR @property def is_remote(self) -> bool: return False def _generate_noisy_input(self): self._inputs_map = self._source.generate_distribution(self.input_state) def generate_noisy_heralds(self) -> SVDistribution: if self.heralds: heralds_perfect_state = FockState([v for k, v in sorted(self.experiment.in_heralds.items())]) return self._source.generate_distribution(heralds_perfect_state) return SVDistribution() def _input_changed_observer(self): if isinstance(self.input_state, BasicState): if isinstance(self.input_state, AnnotatedFockState): self._inputs_map = SVDistribution(StateVector(self.input_state)) else: self._inputs_map = None elif isinstance(self.input_state, SVDistribution): self._inputs_map = self.input_state def clear_input_and_circuit(self, new_m=None): super().clear_input_and_circuit(new_m) self._inputs_map = None def linear_circuit(self, flatten: bool = False) -> Circuit: """ Creates a linear circuit from internal components, if all internal components are unitary. Takes phase imprecision noise into account. :param flatten: if True, the component recursive hierarchy is discarded, making the output circuit "flat". :raises RuntimeError: If any component is non-unitary :return: The resulting Circuit object """ return self.experiment.unitary_circuit(flatten=flatten, use_phase_noise=True) def samples(self, max_samples: int, max_shots: int = None, progress_callback=None) -> dict: self.check_min_detected_photons_filter() from perceval.simulators import NoisySamplingSimulator from perceval.backends import ASamplingBackend assert isinstance(self.backend, ASamplingBackend), "A sampling backend is required to call samples method" sampling_simulator = NoisySamplingSimulator(self.backend) sampling_simulator.sleep_between_batches = 0 # Remove sleep time between batches of samples in local simulation sampling_simulator.set_circuit(self.linear_circuit()) sampling_simulator.set_selection( min_detected_photons_filter=self._min_detected_photons_filter, postselect=self.post_select_fn, heralds=self.heralds) sampling_simulator.keep_heralds(False) sampling_simulator.compute_physical_logical_perf(self._compute_physical_logical_perf) sampling_simulator.set_detectors(self.detectors) self.log_resources(sys._getframe().f_code.co_name, {'max_samples': max_samples, 'max_shots': max_shots}) get_logger().info( f"Start a local {'perfect' if self._source.is_perfect() else 'noisy'} sampling", channel.general) sample_provider = self.source_distribution if self._has_custom_input else (self._source, self.input_state) res = sampling_simulator.samples(sample_provider, max_samples, max_shots, progress_callback) get_logger().info("Local sampling complete!", channel.general) return res def probs(self, precision: float = None, progress_callback: callable = None) -> dict: self.check_min_detected_photons_filter() # assert self._inputs_map is not None, "Input is missing, please call with_inputs()" if self._simulator is None: from perceval.simulators import SimulatorFactory # Avoids a circular import self._simulator = SimulatorFactory.build(self) else: self._simulator.set_circuit(self.linear_circuit() if self.experiment.is_unitary else self.components, self.circuit_size) self._simulator.set_min_detected_photons_filter(self._min_detected_photons_filter) if precision is not None: self._simulator.set_precision(precision) get_logger().info(f"Start a local {'perfect' if self._source.is_perfect() else 'noisy'} strong simulation", channel.general) self._simulator.keep_heralds(False) self._simulator.compute_physical_logical_perf(self._compute_physical_logical_perf) svd = self.source_distribution if self._has_custom_input else (self._source, self.input_state) res = self._simulator.probs_svd(svd, self.detectors, progress_callback) get_logger().info("Local strong simulation complete!", channel.general) self.log_resources(sys._getframe().f_code.co_name, {'precision': precision}) return res @property def available_commands(self) -> list[str]: from perceval.backends import ASamplingBackend return ["samples" if isinstance(self.backend, ASamplingBackend) else "probs"] def log_resources(self, method: str, extra_parameters: dict): """Log resources of the processor :param method: name of the method used :param extra_parameters: extra parameters to log. Extra parameter can be: - max_samples - max_shots - precision """ extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None} my_dict = { 'layer': 'Processor', 'backend': self.backend.name, 'm': self.circuit_size, 'method': method } if isinstance(self.input_state, BasicState): my_dict['n'] = self.input_state.n elif isinstance(self.input_state, StateVector): my_dict['n'] = max(self.input_state.n) elif isinstance(self.input_state, SVDistribution): my_dict['n'] = self.input_state.n_max else: get_logger().error(f"Cannot get n for type {type(self.input_state).__name__}", channel.general) if extra_parameters: my_dict.update(extra_parameters) if self.noise != NoiseModel(): my_dict['noise'] = self.noise.__dict__() get_logger().log_resources(my_dict) def compute_physical_logical_perf(self, value: bool): """ Tells the simulator to compute or not the physical and logical performances when possible :param value: True to compute the physical and logical performances, False otherwise. """ self._compute_physical_logical_perf = value ================================================ FILE: perceval/components/source.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing from exqalibur import BSSamples import exqalibur as xq from perceval.utils import SVDistribution, NoisyFockState, FockState, anonymize_annotations, NoiseModel, global_params from perceval.utils.logging import get_logger, channel DISTINGUISHABLE_KEY = 'distinguishable' INDISTINGUISHABLE_KEY = 'indistinguishable' class Source: r"""Definition of a source We build on a phenomenological model first introduced in ref. [1] where an imperfect quantum-dot based single-photon source is modeled by a statistical mixture of Fock states. The model developed here, first introduced in ref. [2], constructs the input multi-photon state using features specific to Perceval. [1] Pont, Mathias, et al. Physical Review X 12, 031033 (2022). https://doi.org/10.1103/PhysRevX.12.031033 [2] Pont, Mathias, et al. arXiv preprint arXiv:2211.15626 (2022). https://doi.org/10.48550/arXiv.2211.15626 :param emission_probability: probability that the source emits at least one photon :param multiphoton_component: second order intensity autocorrelation at zero time delay :math:`g^{(2)}(0)` :param indistinguishability: 2-photon mean wavepacket overlap :param losses: optical losses :param multiphoton_model: `distinguishable` if additional photons are distinguishable, `indistinguishable` otherwise """ def __init__(self, emission_probability: float = 1, multiphoton_component: float = 0, indistinguishability: float = 1, losses: float = 0, multiphoton_model: str = DISTINGUISHABLE_KEY, # Literal[DISTINGUISHABLE_KEY, INDISTINGUISHABLE_KEY] ) -> None: self._source = xq.Source(emission_probability, multiphoton_component, indistinguishability, losses, multiphoton_model == DISTINGUISHABLE_KEY) self.simplify_distribution = False # Simplify the distribution by anonymizing photon annotations (can be # time-consuming for larger distributions) self._sampler : xq.SourceSampler | None = None @staticmethod def from_noise_model(noise: NoiseModel): if noise is None: return Source() return Source(emission_probability=noise.brightness, multiphoton_component=noise.g2, indistinguishability=noise.indistinguishability, losses=1 - noise.transmittance, multiphoton_model=DISTINGUISHABLE_KEY if noise.g2_distinguishable else INDISTINGUISHABLE_KEY) @property def partially_distinguishable(self): return self._source.partially_distinguishable @property def emission_probability(self): return self._source.emission_probability @property def multiphoton_component(self): return self._source.multiphoton_component @property def indistinguishability(self): return self._source.indistinguishability @property def losses(self): return self._source.losses @property def multiphoton_model(self): return DISTINGUISHABLE_KEY if self._source.is_g2_distinguishable else INDISTINGUISHABLE_KEY def probability_distribution(self, nphotons: int = 1, prob_threshold: float = 0) -> SVDistribution: r"""returns SVDistribution on 1 mode associated to the source :param nphotons: Require `nphotons` in the mode (default 1). :param prob_threshold: Probability threshold under which the resulting state is filtered out. """ return self.generate_distribution(FockState([nphotons]), prob_threshold) def generate_distribution(self, expected_input: FockState, prob_threshold: float = 0) -> SVDistribution: """ Simulates plugging the photonic source on certain modes and turning it on. Computes the input probability distribution :param expected_input: Expected input FockState The properties of the source will alter the input state. A perfect source always delivers the expected state as an input. Imperfect ones won't. :param prob_threshold: Probability threshold under which the resulting state is filtered out. By default, `global_params['min_p']` value is used. """ prob_threshold = max(prob_threshold, global_params['min_p']) get_logger().debug(f"Apply 'Source' noise model to {expected_input}", channel.general) dist = self._source.generate_distribution(expected_input, prob_threshold) if self.simplify_distribution and self.partially_distinguishable: dist = anonymize_annotations(dist, annot_tag='_') return dist def create_iterator(self, expected_input: FockState, min_photons_filter: int = 0) -> xq.SimpleSourceIterator: """ Creates a source iterator that can generate all already separated noisy states according to the probability distribution without representing them in memory. This is far more efficient than computing the whole distribution. Supports a min_photons_filter to avoid generating states having not enough photons. >>> from perceval import BasicState, Source >>> >>> source = Source(indistinguishability=0.85, losses=0.56) >>> iterator = source.create_iterator(BasicState([1, 0, 1]), 2) >>> iterator.prob_threshold = iterator.max_p * 1e-5 # Generates only states having at most 1e-5 times the biggest probability. >>> for separated_state, prob in iterator: >>> print(separated_state, prob) :param expected_input: Expected input BasicState :param min_photons_filter: The minimum number of photons required to generate a state. """ # TODO: chose iterator depending on the input state return self._source.create_simple_iterator(expected_input, min_photons_filter) def create_sampler(self, expected_input: FockState, min_photons_filter: int = 0) -> xq.SourceSampler: """ Creates a source sampler that will be able to generate states according to the source probability distribution :param expected_input: The expected input BasicState to sample. :param min_photons_filter: Minimum number of photons in a sampled state """ return self._source.create_sampler(expected_input, min_photons_filter) def generate_samples(self, max_samples: int, expected_input: FockState, min_detected_photons = 0) -> list[NoisyFockState]: """ Samples states from the source probability distribution without representing the whole distribution in memory. Creates a source sampler and store it in self for faster repeated sampling if necessary. :param max_samples: Number of samples to generate. :param expected_input: The nominal input state that the source should produce. :param min_detected_photons: Minimum number of photons in a sampled state. """ if self.is_perfect(): return [NoisyFockState(expected_input)] * max_samples if self._sampler is None or min_detected_photons != self._sampler.min_photons_filter or expected_input != self._sampler.expected_input: self._sampler = self.create_sampler(expected_input, min_detected_photons) return self._sampler.generate_samples(max_samples) def generate_separated_samples(self, max_samples: int, expected_input: FockState, min_detected_photons = 0) -> list[BSSamples]: """ Samples separated states from the source probability distribution without representing the whole distribution in memory. The sampled states are equivalent (up to permutation) to calling separate_state() on each state returned by generate_samples() but this sampling process uses a simplified procedure to do it faster. Creates a source sampler and store it in self for faster repeated sampling if necessary. :param max_samples: Number of samples to generate. :param expected_input: The nominal input state that the source should produce. :param min_detected_photons: Minimum number of photons in a sampled state. """ if self.is_perfect(): sample = BSSamples([expected_input]) return [sample] * max_samples if self._sampler is None or min_detected_photons != self._sampler.min_photons_filter or expected_input != self._sampler.expected_input: self._sampler = self.create_sampler(expected_input, min_detected_photons) return self._sampler.generate_separated_samples(max_samples) def is_perfect(self) -> bool: return self._source.is_perfect def __eq__(self, value: Source) -> bool: return self._source == value._source and self.simplify_distribution == value.simplify_distribution def to_dict(self) -> dict: return {'g2': self.multiphoton_component, 'transmittance': 1 - self.losses, 'brightness': self.emission_probability, 'indistinguishability': self.indistinguishability, 'g2_distinguishable': self._source.is_g2_distinguishable} ================================================ FILE: perceval/components/tomography_exp_configurer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .processor import AProcessor, Processor from ._pauli import PauliType, PauliEigenStateType, get_pauli_eigen_state_prep_circ, get_pauli_basis_measurement_circuit def _prep_state_circuit_preparer(prep_state_indices: list): """ Generates a layer of state preparation circuits (essentially 1-qubit pauli gates) for each qubit. The logical qubit state prepared will be one of the list: |0>,|1>,|+>,|+i> using Pauli Gates. :param prep_state_indices: List of 'n'(=nqubit) indices to choose one of the logical states for each qubit """ for i, pauli_type in enumerate(prep_state_indices): yield i * 2, get_pauli_eigen_state_prep_circ(pauli_type) def _meas_state_circuit_preparer(pauli_indices: list): """ Generates a layer of state measurement circuits (essentially measuring eigenstates of one of the pauli gates) for each qubit. :param pauli_indices: List of 'n'(=nqubit) indices to choose a circuit to measure the prepared state at nth qubit """ for i, pauli_type in enumerate(pauli_indices): yield i*2, get_pauli_basis_measurement_circuit(pauli_type) def processor_circuit_configurator(processor, prep_state_indices: list, meas_pauli_basis_indices: list): """ Adds preparation and measurement circuit to input processor (with the gate operation under study) to configure it for the tomography experiment :param processor: Processor with input circuit on which Tomography is to be performed :param prep_state_indices: List of "nqubit" indices selecting the circuit at each qubit for a preparation state :param meas_pauli_basis_indices: List of "nqubit" indices selecting the circuit at each qubit for a measurement circuit :return: the configured processor to perform state tomography experiment """ if not isinstance(processor, AProcessor): raise TypeError(f"{processor} is not a Processor and hence cannot be configured") if not all(isinstance(p_index, PauliEigenStateType) for p_index in prep_state_indices): raise TypeError( "Indices for the preparation circuits should be a PauliEigenStateType") if not all(isinstance(m_index, PauliType) for m_index in meas_pauli_basis_indices): raise TypeError( "Indices for the measurement circuits should be a PauliType") experiment = processor.experiment e = experiment.copy() e.clear_input_and_circuit(experiment.m) # Clear experiment content but keep its size for c in _prep_state_circuit_preparer(prep_state_indices): e.add(*c) # Add state preparation circuit to the left of the operator e.add(0, experiment) # including the operator (as an experiment) for c in _meas_state_circuit_preparer(meas_pauli_basis_indices): e.add(*c) # Add measurement basis circuit to the right of the operator e.min_detected_photons_filter(0) # QPU would have a problem with this return Processor(processor.backend, e) ================================================ FILE: perceval/components/unitary_components.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import random from copy import copy from enum import IntEnum import numpy as np import sympy as sp from .linear_circuit import ACircuit, Circuit from perceval.utils import Matrix, format_parameters, BasicState, StateVector, Parameter, Expression class BSConvention(IntEnum): """Beam splitter conventions""" Rx = 0 Ry = 1 H = 2 class BS(ACircuit): """Beam Splitter Beam splitters couple two spatial modes together, acting on :math:`\\ket{1,0}` and :math:`\\ket{0,1}`. :param theta: `theta` parameter :param phi_tl: top-left phase parameter :param phi_bl: bottom-left phase parameter :param phi_tr: top-right phase parameter :param phi_br: bottom-right phase parameter """ DEFAULT_NAME = "BS" def __init__(self, theta: Parameter | float = sp.pi/2, phi_tl: Parameter | float = 0, phi_bl: Parameter | float = 0, phi_tr: Parameter | float = 0, phi_br: Parameter | float = 0, convention: BSConvention = BSConvention.Rx): super().__init__(2) self._convention = convention self._theta = self._set_parameter("theta", theta, 0, 4*sp.pi) self._phi_tl = self._set_parameter("phi_tl", phi_tl, 0, 2*sp.pi) self._phi_bl = self._set_parameter("phi_bl", phi_bl, 0, 2*sp.pi) self._phi_tr = self._set_parameter("phi_tr", phi_tr, 0, 2*sp.pi) self._phi_br = self._set_parameter("phi_br", phi_br, 0, 2*sp.pi) @property def name(self): return f'{self.DEFAULT_NAME}.{self._convention.name}' @property def convention(self): """Beam splitter convention""" return self._convention @staticmethod def H(theta=sp.pi/2, phi_tl=0, phi_bl=0, phi_tr=0, phi_br=0): """ Convenient named constructor for a Beam Splitter following Hadamard convention. Its parameters are the same as the main constructor. """ return BS(theta, phi_tl, phi_bl, phi_tr, phi_br, convention=BSConvention.H) @staticmethod def Rx(theta=sp.pi / 2, phi_tl=0, phi_bl=0, phi_tr=0, phi_br=0): """ Convenient named constructor for a Beam Splitter following Rotation X convention. Its parameters are the same as the main constructor. """ return BS(theta, phi_tl, phi_bl, phi_tr, phi_br, convention=BSConvention.Rx) @staticmethod def Ry(theta=sp.pi / 2, phi_tl=0, phi_bl=0, phi_tr=0, phi_br=0): """ Convenient named constructor for a Beam Splitter following Rotation Y convention. Its parameters are the same as the main constructor. """ return BS(theta, phi_tl, phi_bl, phi_tr, phi_br, convention=BSConvention.Ry) @staticmethod def r_to_theta(r: float | Parameter) -> float | Expression: """Compute theta given a reflectivity value. Supports symbolic computing. :param r: reflectivity value (can be variable) :return: theta value or symbolic expression """ if isinstance(r, Parameter): return Expression(f"2*acos(sqrt({r.name}))", r._params) return 2*math.acos(math.sqrt(r)) @staticmethod def theta_to_r(theta: float | Parameter) -> float | Expression: """ Compute reflectivity given a theta value. Supports symbolic computing. :param theta: theta angle (can be variable) :return: reflectivity value or symbolic expression """ if isinstance(theta, Parameter) and not theta.defined: return Expression(f"cos({theta.name}/2)**2", theta._params) return math.cos(float(theta)/2)**2 @property def reflectivity(self): """Beam Splitter reflectivity :return: reflectivity of the current Beam Splitter """ return self.theta_to_r(self._theta) def _compute_unitary(self, assign=None, use_symbolic=False): if use_symbolic: theta = self._theta.spv cos_theta = sp.cos(theta/2) sin_theta = sp.sin(theta/2) phi_tl = self._phi_tl.spv phi_tr = self._phi_tr.spv phi_bl = self._phi_bl.spv phi_br = self._phi_br.spv u00_mul = sp.exp((phi_tl + phi_tr)*sp.I) u01_mul = sp.exp((phi_tr + phi_bl)*sp.I) u10_mul = sp.exp((phi_tl + phi_br)*sp.I) u11_mul = sp.exp((phi_br + phi_bl)*sp.I) else: cos_theta = math.cos(float(self._theta)/2) sin_theta = math.sin(float(self._theta)/2) phi_tl_tr = float(self._phi_tl) + float(self._phi_tr) u00_mul = math.cos(phi_tl_tr) + 1j*math.sin(phi_tl_tr) phi_tr_bl = float(self._phi_tr) + float(self._phi_bl) u01_mul = math.cos(phi_tr_bl) + 1j*math.sin(phi_tr_bl) phi_tl_br = float(self._phi_tl) + float(self._phi_br) u10_mul = math.cos(phi_tl_br) + 1j*math.sin(phi_tl_br) phi_bl_br = float(self._phi_bl) + float(self._phi_br) u11_mul = math.cos(phi_bl_br) + 1j*math.sin(phi_bl_br) umat = self._matrix_template(use_symbolic) umat[0, 0] *= u00_mul*cos_theta umat[0, 1] *= u01_mul*sin_theta umat[1, 1] *= u11_mul*cos_theta umat[1, 0] *= u10_mul*sin_theta return umat def _matrix_template(self, use_symbolic): if self._convention == BSConvention.Rx: if use_symbolic: return Matrix([[1, sp.I], [sp.I, 1]], True) return Matrix([[1, 1j], [1j, 1]], False) elif self._convention == BSConvention.Ry: return Matrix([[1, -1], [1, 1]], use_symbolic) elif self._convention == BSConvention.H: return Matrix([[1, 1], [1, -1]], use_symbolic) raise NotImplementedError(f'Unitary matrix computation not implemented for convention {self._convention.name}') def get_variables(self): out = {} self._populate_parameters(out, "theta", sp.pi / 2) self._populate_parameters(out, "phi_tl", 0) self._populate_parameters(out, "phi_bl", 0) self._populate_parameters(out, "phi_tr", 0) self._populate_parameters(out, "phi_br", 0) return out def describe(self) -> str: parameters = self.get_variables() params_str = format_parameters(parameters, separator=', ') return f"BS.{self._convention.name}({params_str})" def inverse(self, v=False, h=False): theta = float(self._theta) if self._theta.defined else self._theta phi_bl = float(self._phi_bl) if self._phi_bl.defined else self._phi_bl phi_tl = float(self._phi_tl) if self._phi_tl.defined else self._phi_tl phi_tr = float(self._phi_tr) if self._phi_tr.defined else self._phi_tr phi_br = float(self._phi_br) if self._phi_br.defined else self._phi_br if v: self._phi_bl.set_value(phi_bl, force=True) if self._phi_bl.defined else None self._phi_tr.set_value(phi_tr, force=True) if self._phi_tr.defined else None self._phi_tl.set_value(phi_tl, force=True) if self._phi_tl.defined else None self._phi_br.set_value(phi_br, force=True) if self._phi_br.defined else None # For Rx BS, vertical inversion does not impact theta parameter if self._convention == BSConvention.Ry: if self._theta.defined: self._theta.set_value(-theta, force=True) else: self._theta = -theta elif self._convention == BSConvention.H: if self._theta.defined: self._theta.set_value(2*math.pi - float(self._theta), force=True) else: self._theta = 2*math.pi - theta if h: for param in [self._phi_tl, self._phi_bl, self._phi_tr, self._phi_br]: if param.defined: param.set_value(-float(param), force=True) else: self._set_parameter(param.name, -param, 0, 4*sp.pi) # For H BS, horizontal inversion does not impact theta parameter if self._convention == BSConvention.Rx or self._convention == BSConvention.Ry: if self._theta.defined: self._theta.set_value(-theta, force=True) else: self._theta = self._set_parameter("theta", -theta, 0, 4*sp.pi) def definition(self): return BS(Parameter('theta'), Parameter('phi_tl'), Parameter('phi_bl'), Parameter('phi_tr'), Parameter("phi_br"), self._convention).U class PS(ACircuit): """Phase shifter A phase shifter adds a phase :math:`\\phi` on a spatial mode, which corresponds to a Z rotation in the Bloch sphere. :param phi: Phase angle :param max_error: Maximum random error to apply. The error is uniformly drawn in :math:`[\\phi - max_{error}, \\phi + max_{error}]`. A global phase error noise parameter can also be set in the `NoiseModel` for all the phase shifters of a given `Experiment`. """ DEFAULT_NAME = "PS" def __init__(self, phi: Parameter | float, max_error: Parameter | float = 0): super().__init__(1) self._phi = self._set_parameter("phi", phi, 0, 2*math.pi) self._max_error = self._set_parameter("max_error", max_error, 0, math.pi) def _compute_unitary(self, assign=None, use_symbolic=False): self.assign(assign) if use_symbolic: err = self._max_error.spv*random.uniform(-1, 1) phase = self._phi.spv + err return Matrix([[sp.exp(phase * sp.I)]], True) else: err = float(self._max_error)*random.uniform(-1, 1) phase = float(self._phi) + err return Matrix([[math.cos(phase) + 1j * math.sin(phase)]], False) def get_variables(self): out = {} self._populate_parameters(out, "phi") if self._max_error: self._populate_parameters(out, "max_error") return out def describe(self): params_str = format_parameters(self.get_variables(), separator=', ') return f"PS({params_str})" def inverse(self, v=False, h=False): if h: if self._phi.is_symbolic: self._phi = self._set_parameter("phi", -self._phi, None, None) else: self._phi.set_value(-float(self._phi), force=True) def definition(self) -> Matrix: phase_param = Expression("Uniform(-1, 1) * max_error + phi", {Parameter('max_error'), Parameter("phi")}) return PS(phase_param).U class WP(ACircuit): """ A wave plate acts on the polarisation modes of a single spatial mode. This component acts on polarised photons. See also: `Polarisation` :param delta: parameter proportional to the thickness of the waveplate :param xsi: angle of the waveplate's optical axis in the :math:`\\left\\{\\ket{H}, \\ket{V}\\right\\}` plane. Especially important is the case that :math:`\\delta=\\pi/2`, known as a half-wave plate, which rotates linear polarisations in the :math:`\\left\\{\\ket{H}, \\ket{V}\\right\\}` plane. """ DEFAULT_NAME = "WP" _supports_polarization = True def __init__(self, delta: float | Parameter, xsi: float | Parameter): super().__init__(1) self._delta = self._set_parameter("delta", delta, -sp.pi, sp.pi) self._xsi = self._set_parameter("xsi", xsi, -sp.pi, sp.pi) def _compute_unitary(self, assign=None, use_symbolic=False): self.assign(assign) if use_symbolic: delta = self._delta.spv xsi = self._xsi.spv return Matrix([[ sp.cos(delta)+sp.I*sp.sin(delta)*sp.cos(2*xsi), sp.I*sp.sin(delta)*sp.sin(2*xsi) ], [ sp.I * sp.sin(delta) * sp.sin(2 * xsi), sp.cos(delta) - sp.I * sp.sin(delta) * sp.cos(2 * xsi) ]], True) else: delta = float(self._delta) xsi = float(self._xsi) return Matrix([[ math.cos(delta)+1j*math.sin(delta)*math.cos(2*xsi), 1j*math.sin(delta)*math.sin(2*xsi) ], [ 1j * math.sin(delta) * math.sin(2 * xsi), math.cos(delta) - 1j * math.sin(delta) * math.cos(2 * xsi) ]], False) def get_variables(self): out = {} self._populate_parameters(out, "xsi") self._populate_parameters(out, "delta") return out def describe(self): params_str = format_parameters(self.get_variables(), separator=', ') return f"WP({params_str})" def inverse(self, v=False, h=False): raise NotImplementedError("inverse not yet implemented") class HWP(WP): """Half wave plate This component acts on polarized photons. See also: `Polarization` """ DEFAULT_NAME = "HWP" def __init__(self, xsi): super().__init__(sp.pi/2, xsi) def definition(self): return HWP(xsi=Parameter('xsi')).U class QWP(WP): """Quarter wave plate This component acts on polarized photons. See also: `Polarization` """ DEFAULT_NAME = "QWP" def __init__(self, xsi): super().__init__(sp.pi/4, xsi) def definition(self): return QWP(xsi=Parameter('xsi')).U class PR(ACircuit): """ A polarisation rotator is an optical device that rotates the polarization axis of a linearly polarized light beam by an angle of choice. Such devices can be based on the Faraday effect, on bi-refringence, or on total internal reflection. Rotators of linearly polarized light have found widespread applications in modern optics since laser beams tend to be linearly polarized. It is often necessary to rotate the original polarization to its orthogonal alternative. This component acts on polarized photons. See also: `Polarization` See https://en.wikipedia.org/wiki/Polarization_rotator for more details. :param delta: Rotation angle """ _supports_polarization = True DEFAULT_NAME = "PR" def __init__(self, delta: float | Parameter): super().__init__(1) self._delta = self._set_parameter("delta", delta, -sp.pi, sp.pi) def _compute_unitary(self, assign=None, use_symbolic=False): self.assign(assign) if use_symbolic: delta = self._delta.spv return Matrix([[sp.cos(delta), sp.sin(delta)], [-sp.sin(delta), sp.cos(delta)]], True) else: delta = float(self._delta) return Matrix([[math.cos(delta), math.sin(delta)], [-math.sin(delta), math.cos(delta)]], False) def get_variables(self): out = {} self._populate_parameters(out, "delta") return out def describe(self): params_str = format_parameters(self.get_variables(), separator=', ') return f"PR({params_str})" def inverse(self, v: bool = False, h: bool = False): raise NotImplementedError("inverse not yet implemented") class Unitary(ACircuit): """Generic component defined by a unitary matrix :param U: numeric matrix. Does not support symbolic computation. :param name: Custom name for the component it represents (default is "Unitary"). :param use_polarization: True if the unitary represents a polarized component. """ DEFAULT_NAME = "Unitary" def __init__(self, U: Matrix, name: str = None, use_polarization: bool = False): assert U is not None, "A unitary matrix is required" assert U.is_unitary(), "U parameter must be a unitary matrix" # A symbolic matrix is not a use case for this component assert not U.is_symbolic(), "U parameter must not be symbolic" self._u = U m = U.shape[0] self._supports_polarization = use_polarization if use_polarization: assert m % 2 == 0, "Polarization matrix should have an even number of rows/col" m //= 2 super().__init__(m, name) def _compute_unitary(self, assign: dict = None, use_symbolic: bool = False) -> Matrix: # Ignore assign and use_symbolic parameters as __init__ checked the unitary matrix is numeric return self._u def inverse(self, v=False, h=False): if v: self._u = np.flip(self._u) if h: self._u = self._u.inv() def describe(self): params = [f"Matrix('''{self._u}''')"] if self.name != Unitary.DEFAULT_NAME: params.append(f"name='{self._name}'") if self._supports_polarization: params.append("use_polarization=True") return f"Unitary({', '.join(params)})" class PERM(Unitary): """Permutation A swap between any number of consecutive spatial modes. :param perm: Vector of mode index defining the permutation. >>> permutation = PERM([2, 3, 1, 0]) # respectively swaps mode 0 to 2, 1 to 3, 2 to 1 and 3 to 0. """ DEFAULT_NAME = "PERM" def __init__(self, perm: list[int]): assert isinstance(perm, list), "Permutation component requires a list parameter" assert (min(perm) == 0 and max(perm)+1 == len(perm) == len(set(perm)) == len([n for n in perm if isinstance(n, int)])),\ "%s is not a permutation" % perm n = len(perm) u = Matrix.zeros((n, n), use_symbolic=False) for i, v in enumerate(perm): u[v, i] = 1 super().__init__(U=u) def describe(self): return f"PERM({self.perm_vector})" def definition(self): return self.U @property def perm_vector(self): """Return the permutation vector""" nz = np.nonzero(self._u) m_list = nz[1].tolist() return [m_list.index(i) for i in nz[0]] def apply(self, r: tuple[int, ...], sv: BasicState | StateVector): """ Apply the permutation to a state :param r: Range of consecutive modes where the permutation occurs :sv: State on which the permutation is applied """ if isinstance(sv, BasicState): sv = StateVector(sv) min_r = r[0] max_r = r[-1] + 1 permutation = self.perm_vector inv = np.empty_like(permutation) inv[permutation] = np.arange(len(inv), dtype=inv.dtype) inv = [inv[i].item() for i in range(len(inv))] nsv = copy(sv) nsv.clear() nsv.update({ BasicState(state.set_slice(slice(min_r, max_r), BasicState([state[i + min_r] for i in inv]))): prob_ampli for state, prob_ampli in sv.items() }) return nsv def break_in_2_mode_perms(self): """ Breaks any n-mode permutation into an equivalent circuit made of only 2-mode permutations. :return: An equivalent Circuit made of only 2-mode permutations """ perm_vec_req = self.perm_vector perm_len = len(perm_vec_req) if perm_len == 2: return self circ = Circuit(perm_len, name="Decomposed PERM") new_perm_vec = list(range(perm_len)) for in_m_pos in range(perm_len): out_m_pos = perm_vec_req.index(in_m_pos) while new_perm_vec[in_m_pos] != out_m_pos: swap_idx = new_perm_vec.index(out_m_pos) new_perm_vec[swap_idx], new_perm_vec[swap_idx - 1] = new_perm_vec[swap_idx - 1], new_perm_vec[swap_idx] circ.add(swap_idx - 1, PERM([1, 0])) return circ class PBS(Unitary): """ A polarising beam splitter converts a superposition of polarisation modes in a single spatial mode to the corresponding equal-polarisation superposition of two spatial modes, and vice versa, and so in this sense allows us to translate between polarisation and spatial modes. The unitary matrix associated to a polarising beam splitter acting on the tensor product of the spatial mode and the polarisation mode is: :math:`\\left[\\begin{matrix}0 & 0 & 1 & 0\\\\0 & 1 & 0 & 0\\\\1 & 0 & 0 & 0\\\\0 & 0 & 0 & 1\\end{matrix}\\right]` This component acts on polarized photons. See also: `Polarization` """ _supports_polarization = True DEFAULT_NAME = "PBS" def __init__(self): u = Matrix([[0, 0, 1, 0], [0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1]]) super().__init__(U=u, use_polarization=True) # noinspection PyMethodMayBeStatic def describe(self): return "PBS()" class Barrier(ACircuit): """ A barrier is a visual component which has no effect on photons (it behaves as an identity unitary). It may be used to separate or align multiple components in a given `Circuit`. :param m: Number of consecutive modes it covers :param visible: The barrier is rendered if True, and is invisible otherwise """ DEFAULT_NAME = "I" def __init__(self, m: int, visible: bool = True): assert isinstance(m, int), "Barrier() first parameter has to be an integer (mode count)" self._visible = bool(visible) super().__init__(m) @property def visible(self): return self._visible def _compute_unitary(self, assign: dict = None, use_symbolic: bool = False) -> Matrix: return Matrix.eye(self._m) def describe(self): return f"Barrier({self._m})" def definition(self): return self.U # noinspection PyMethodMayBeStatic def apply(self, r, sv): return sv def inverse(self, v=False, h=False): pass ================================================ FILE: perceval/error_mitigation/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .loss_mitigation import photon_recycling ================================================ FILE: perceval/error_mitigation/_loss_mitigation_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np from math import comb from copy import copy from itertools import combinations, cycle from scipy.optimize import curve_fit MAX_LOST_PHOTONS = 2 def check_no_collision(state) -> bool: # verifies absence of bunching in a given state return all(i <= 1 for i in state) def _handle_zero_photon_lost_dist(noisy_distributions, pattern_map, noisy_state, count): index = pattern_map[tuple(noisy_state)] noisy_distributions[0][index] += count def _handle_one_photon_lost_dist(noisy_distributions, pattern_map, noisy_state, count): for t in range(noisy_state.m): # loop through each bit in string and +1 in each place n_ls = list(noisy_state) n_ls[t] += 1 if check_no_collision(n_ls): index = pattern_map[tuple(n_ls)] noisy_distributions[1][index] += count def _handle_two_photons_lost_dist(noisy_distributions, pattern_map, noisy_state, count): for t in range(noisy_state.m): n_ls = list(noisy_state) n_ls[t] += 1 for r in range(t, noisy_state.m): n_ls1 = copy(n_ls) n_ls1[r] += 1 if check_no_collision(n_ls1): # if non-collision is true index = pattern_map[tuple(n_ls1)] noisy_distributions[2][index] += count def _generate_one_photon_per_mode_mapping(m, n): combos = combinations(range(m), m - n) ones_photons = [1] * n char_cyc = cycle(ones_photons) perms = [tuple(0 if i in combo else next(char_cyc) for i in range(m)) for combo in combos] return {perm: index for perm, index in zip(perms, range(len(perms)))} def _gen_lossy_dists(noisy_input, ideal_photon_count, pattern_map): # Takes non-collision (no bunching) samples as input and # outputs approximate distributions for each number of lost photon statistics. # MAX_LOST_PHOTONS controls upto how many are considered noisy_distributions = [np.zeros(len(pattern_map)) for _ in range(MAX_LOST_PHOTONS + 1)] for noisy_state, count in noisy_input.items(): # loop through all the noisy states if noisy_state.n < (ideal_photon_count - MAX_LOST_PHOTONS) or not check_no_collision(noisy_state): continue actual_photon_count = noisy_state.n if actual_photon_count == ideal_photon_count: _handle_zero_photon_lost_dist(noisy_distributions, pattern_map, noisy_state, count) elif actual_photon_count == ideal_photon_count - 1: _handle_one_photon_lost_dist(noisy_distributions, pattern_map, noisy_state, count) elif actual_photon_count == ideal_photon_count - 2: _handle_two_photons_lost_dist(noisy_distributions, pattern_map, noisy_state, count) for i in range(MAX_LOST_PHOTONS + 1): summed = sum(noisy_distributions[i]) if summed > 0: noisy_distributions[i] = noisy_distributions[i] / summed return noisy_distributions def _get_avg_exp_from_uni_dist(noisy_distributions, m, n): # fits a noisy data to provide parameters to generate mitigated distribution def func(x, b): return uni_value * np.exp(-b * x) uniform_prob = 1 / comb(m, n) noisy_distributions_from_uni = [np.average(abs(noisy_distribution - uniform_prob)) for noisy_distribution in noisy_distributions] uni_value = noisy_distributions_from_uni[0] z, _ = curve_fit(f=func, xdata=[0, 1, 2, 50], ydata=[uni_value, noisy_distributions_from_uni[1], noisy_distributions_from_uni[2], 0], bounds=([-5], [5]), maxfev=2000000) return z ================================================ FILE: perceval/error_mitigation/loss_mitigation.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np from math import comb from scipy.optimize import curve_fit from perceval.utils import BSCount, BSDistribution, BasicState from perceval.utils.logging import get_logger, channel from ._loss_mitigation_utils import _gen_lossy_dists, _get_avg_exp_from_uni_dist, _generate_one_photon_per_mode_mapping def _validate_noisy_input(noisy_input: BSCount | BSDistribution, ideal_photon_count: int): if not isinstance(noisy_input, (BSCount, BSDistribution)): # check if the input type is correct raise TypeError('Noisy input should be of type BSCount or BSDistribution') if all([states.n == ideal_photon_count for states in noisy_input.keys()]): # Check if a perfect loss-less distribution was passed to mitigate raise ValueError("All input states have ideal photon count, no loss to mitigate.") target_photon_loss = {ideal_photon_count-1, ideal_photon_count-2} obtained_photon_counts = {states.n for states in noisy_input.keys()} if not target_photon_loss.issubset(obtained_photon_counts): # check if noisy input meets current implementation's statistics criteria raise ValueError("Noisy input invalid or incorrect ideal photon count, " "implementation requires states with n-1, n-2 photons for expected n-photon mitigation") def photon_recycling(noisy_input: BSCount | BSDistribution, ideal_photon_count: int) -> BSDistribution: """ A classical technique to mitigate errors in the output distribution caused by photon loss in LO quantum circuits (ref: https://arxiv.org/abs/2405.02278) :param noisy_input: Noisy output (Basic State Samples or a distribution) :param ideal_photon_count: expected photon count for a loss-less system :return: photon loss mitigated distribution """ get_logger().info(f"Running Photon Recycling on a {len(noisy_input)} states distribution targetting {ideal_photon_count} ideal photons", channel.general) # run checks on noisy input before recycling _validate_noisy_input(noisy_input, ideal_photon_count) m = next(iter(noisy_input)).m # number of modes pattern_map = _generate_one_photon_per_mode_mapping(m, ideal_photon_count) noisy_distributions = _gen_lossy_dists(noisy_input, ideal_photon_count, pattern_map) # GET AVERAGE EXPONENT USING AVERAGE DISTANCE FROM UNIFORM PROBABILITY z = _get_avg_exp_from_uni_dist(noisy_distributions, m, ideal_photon_count) median_of_means = z[0] # Generating the mitigated distribution using the decay parameter. mitigated_probs = [] c_mn_inv = 1 / comb(m, ideal_photon_count) def func1(x, a): return a * np.exp(-median_of_means * x) + c_mn_inv for k in range(len(noisy_distributions[0])): z, _ = curve_fit(f=func1, xdata=[1, 2, 50], ydata=[noisy_distributions[1][k], noisy_distributions[2][k], c_mn_inv], bounds=([-5], [5]), maxfev=2000000) if noisy_distributions[1][k] > c_mn_inv > noisy_distributions[2][k]: mitigated_probs.append(c_mn_inv) elif noisy_distributions[1][k] < c_mn_inv < noisy_distributions[2][k]: mitigated_probs.append(c_mn_inv) else: mitigated_probs.append(func1(0, z[0])) mitigated_probs = [0 if i < 0 else i for i in mitigated_probs] mitigated_probs = mitigated_probs / np.sum(mitigated_probs) mitigated_distribution = BSDistribution() for index, keys in enumerate(pattern_map.keys()): state = BasicState(keys) mitigated_distribution.add(state, mitigated_probs[index]) return mitigated_distribution ================================================ FILE: perceval/providers/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """Provider related imports and classes""" from perceval.runtime import ISession from .quandela import Session as QuandelaSession from .scaleway import Session as ScalewaySession PROVIDER_LIST = { "Quandela": QuandelaSession, "Scaleway": ScalewaySession, } class ProviderFactory: @staticmethod def get_provider(provider_name: str, **kwargs) -> ISession: name = provider_name if name in PROVIDER_LIST: return PROVIDER_LIST[name](**kwargs) raise KeyError(f'Cloud Provider "{name}" not found, available providers are: {ProviderFactory.list()}.') @staticmethod def list(): return list(PROVIDER_LIST.keys()) ================================================ FILE: perceval/providers/quandela/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .quandela_session import Session ================================================ FILE: perceval/providers/quandela/quandela_session.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.runtime import ISession from perceval.runtime.remote_processor import RemoteProcessor from perceval.runtime.remote_config import QUANDELA_CLOUD_URL from perceval.runtime.rpc_handler import RPCHandler from perceval.utils.logging import get_logger, channel class Session(ISession): """ Quandela Cloud session :param platform_name: Name of an available platform on Quandela Cloud (e.g. "sim:slos") :param token: A valid authentication token to use within the session :param url: URL prefix for the endpoint to connect to. If omitted the Quandela Cloud endpoints will be used. """ def __init__(self, platform_name: str, token: str, url: str = None): self._platform_name = platform_name self._token = token self._url = url or QUANDELA_CLOUD_URL get_logger().info(f"Creating Quandela Session to {self._url}", channel.general) def build_remote_processor(self) -> RemoteProcessor: """Build a remote processor from the session information""" return RemoteProcessor(rpc_handler=RPCHandler(self._platform_name, self._url, self._token)) ================================================ FILE: perceval/providers/scaleway/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .scaleway_session import Session from .scaleway_rpc_handler import RPCHandler __all__ = ["Session", "RPCHandler"] ================================================ FILE: perceval/providers/scaleway/scaleway_rpc_handler.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import urllib import time import requests import json from datetime import datetime, timedelta from requests import HTTPError from perceval.utils.logging import get_logger, channel _ENDPOINT_PLATFORM = "/qaas/v1alpha1/platforms" _ENDPOINT_JOB = "/qaas/v1alpha1/jobs" _ENDPOINT_SESSION = "/qaas/v1alpha1/sessions" _DEFAULT_URL = "https://api.scaleway.com" _DEFAULT_PLATFORM_PROVIDER = "quandela" class RPCHandler: """Stateful (1 session at a time) RPCHandler for Scaleway Cloud provider""" def __init__( self, project_id: str, secret_key: str, url: str, proxies: dict, platform_name: str, provider_name: str, ): self._project_id = project_id self._url = url or _DEFAULT_URL self._proxies = proxies or dict() self._session_id = None self._platform_name = platform_name self._headers = { "X-Auth-Token": secret_key, } self._provider_name = provider_name or _DEFAULT_PLATFORM_PROVIDER self._platform_id = self.get_platform( platform_name=self._platform_name, provider_name=self._provider_name )["id"] @property def name(self) -> str: return self._platform_name @property def url(self) -> str: return self._url @property def headers(self) -> dict: return self._headers @property def proxies(self) -> dict: return self._proxies def fetch_platform_details(self) -> dict: return self.get_platform(self._platform_name, self._provider_name) def get_platform(self, platform_name: str, provider_name: str) -> dict: endpoint = f"{self.__build_endpoint(_ENDPOINT_PLATFORM)}?providerName={provider_name}&name={urllib.parse.quote_plus(platform_name)}" resp = requests.get(endpoint, headers=self._headers, proxies=self._proxies) resp.raise_for_status() platform_resp = resp.json() if "platforms" not in platform_resp: raise HTTPError( f"platforms '{platform_name}' not found for provider '{provider_name}'" ) platforms = platform_resp["platforms"] if len(platforms) == 0: raise HTTPError(f"Empty platform list for provider '{provider_name}'") platform = platforms[0] platform["specs"] = json.loads(platform.get("metadata", {})) return platform def create_session( self, deduplication_id: str, max_duration_s: int, max_idle_duration_s: int, ) -> str: """Start a session, when started job can be sent and executed on it :param provider_name: the quantum provider to target, quandela by default :param platform_name: the platform to built the session from :param deduplcation_id: the session creation will return any running session with that id :param max_duration_s: max duration (in second) before the session is terminated :param max_idle_duration_s: max duration (in second) before the session is terminated """ if self._session_id: raise Exception("A session is already attached to this RPC handler") payload = { "project_id": self._project_id, "platform_id": self._platform_id, "deduplication_id": deduplication_id, "max_duration": f"{max_duration_s}s", "max_idle_duration": f"{max_idle_duration_s}s", "name": f"pcvl-{datetime.now():%Y-%m-%d-%H-%M-%S}", } endpoint = f"{self._url}{_ENDPOINT_SESSION}" request = requests.post( endpoint, headers=self._headers, json=payload, proxies=self._proxies ) try: request.raise_for_status() session = request.json() self._session_id = session["id"] get_logger().info("Start Scaleway Session", channel.general) except Exception: raise HTTPError(request.json()) return self._session_id def terminate_session(self) -> None: """Stop the attached session, all jobs will be cancelled and still accessible""" if not self._session_id: raise Exception("Cannot terminate session because session_id is None") endpoint = f"{self._url}{_ENDPOINT_SESSION}/{self._session_id}/terminate" request = requests.post(endpoint, headers=self._headers, proxies=self._proxies) request.raise_for_status() def delete_session(self) -> None: """Stop and delete the attached session, all jobs will be deleted""" if not self._session_id: raise Exception("Cannot delete session because session_id is None") endpoint = f"{self._url}{_ENDPOINT_SESSION}/{self._session_id}" request = requests.delete( endpoint, headers=self._headers, proxies=self._proxies ) request.raise_for_status() def create_job(self, payload: dict) -> str: """Create and start on new job on the attached session :param payload: the perceval circuit and run parameters to be executed on the attached session """ if not self._session_id: raise Exception("Cannot create job because session_id is None") scw_payload = { "name": payload.get("job_name"), "circuit": {"percevalCircuit": json.dumps(payload.get("payload", {}))}, "project_id": self._project_id, "session_id": self._session_id, } endpoint = f"{self._url}{_ENDPOINT_JOB}" request = requests.post( endpoint, headers=self._headers, json=scw_payload, proxies=self._proxies ) try: request.raise_for_status() job = request.json() self.instance_id = job["id"] except Exception: raise HTTPError(request.json()) return job["id"] def cancel_job(self, job_id: str) -> None: """Cancel the running job :param job_id: job id to cancel """ endpoint = f"{self.__build_endpoint(_ENDPOINT_JOB)}/{job_id}/cancel" request = requests.post(endpoint, headers=self._headers, proxies=self._proxies) request.raise_for_status() def rerun_job(self, job_id: str) -> str: """Rerun a job :param job_id: job id to rerun :return: new job id """ raise NotImplementedError( "rerun_job method is not implemented for Scaleway RPCHandler" ) def get_job_status(self, job_id: str) -> dict: """Fetch the current job status :param job_id: job id to fetch for :return: dictionary of the current job status """ endpoint = f"{self.__build_endpoint(_ENDPOINT_JOB)}/{job_id}" # requests may throw an IO Exception, let the user deal with it resp = requests.get(endpoint, headers=self._headers, proxies=self._proxies) resp.raise_for_status() job = resp.json() created_at = self.__to_date(job.get("created_at")) started_at = self.__to_date(job.get("started_at")) duration = self.__get_duration(started_at) status = job.get("status") if status == "cancelled": status = "canceled" elif status == "cancelling": status = "cancel_requested" return { "creation_datetime": created_at, "duration": duration, "failure_code": None, "last_intermediate_results": None, "msg": "ok", "progress": 1 if status == "completed" else 0, "progress_message": job.get("progress_message"), "start_time": started_at, "status": status, "status_message": job.get("progress_message"), } def get_job_results(self, job_id: str) -> dict: """Get result of the job execution :param job_id: job id to fetch for :return: dictionary of the results """ endpoint = f"{self.__build_endpoint(_ENDPOINT_JOB)}/{job_id}/results" # requests may throw an IO Exception, let the user deal with it resp = requests.get(endpoint, headers=self._headers, proxies=self._proxies) resp.raise_for_status() resp_dict = resp.json() result_payload = None duration = None results = resp_dict.get("job_results", []) if len(results) > 0: first_result = results[0] duration = self.__get_duration( self.__to_date(first_result.get("created_at")) ) result = first_result.get("result", None) if result is None or result == "": url = first_result.get("url", None) if url is not None: resp = requests.get(url, proxies=self._proxies) resp.raise_for_status() result_payload = resp.text else: raise Exception("Got result with empty 'result' and 'url' fields") else: result_payload = result return { "duration": duration, "intermediate_results": [], "job_id": resp_dict.get("job_id"), "results": result_payload, "results_type": None, } def __build_endpoint(self, endpoint) -> str: return f"{self._url}{endpoint}" def __to_date(self, date: str) -> float | None: if not date: return None # Compat for python 3.10 if date.endswith("Z"): date = date[:-1] return datetime.fromisoformat(date).timestamp() def __get_duration(self, start_time: float | None) -> int | None: return ( timedelta(seconds=time.time() - start_time).seconds if start_time else None ) ================================================ FILE: perceval/providers/scaleway/scaleway_session.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from typing import Optional from perceval.runtime import ISession from perceval.runtime.remote_processor import RemoteProcessor from perceval.utils.logging import get_logger, channel from .scaleway_rpc_handler import RPCHandler class Session(ISession): """ Scaleway session used to keep a connexion opened with Scaleway Cloud for the duration of a Python scope. :param platform_name: platform on which circuits will be executed :param project_id: UUID of the Scaleway Project the session is attached to :param token: authentication token required to access the Scaleway API :param deduplication_id: optional value, name mapping to a unique running session, allowing to share an alive session among multiple users :param max_idle_duration_s: optional value, duration in seconds that can elapsed without activity before the session terminates :param max_duration_s: optional value, duration in seconds for a session before it automatically terminates :param url: optional value, endpoint URL of the API :param proxies: optional value, dictionary mapping protocol to the URL of the proxy """ def __init__( self, platform_name: str, project_id: str, token: str, max_idle_duration_s: int = 1200, max_duration_s: int = 3600, deduplication_id: Optional[str] = None, url: Optional[str] = None, proxies: Optional[dict[str, str]] = None, provider_name: Optional[str] = None, ) -> None: if not platform_name: raise Exception("platform_name cannot be None") if not project_id: raise Exception("project_id cannot be None") if not token: raise Exception("token cannot be None") if not isinstance(max_duration_s, int): raise TypeError("max_duration_s cannot be an int (ie: seconds)") if not isinstance(max_idle_duration_s, int): raise TypeError("max_idle_duration_s cannot be an int (ie: seconds)") self._deduplication_id = deduplication_id self._max_idle_duration_s = max_idle_duration_s self._max_duration_s = max_duration_s self._rpc_handler = RPCHandler( project_id=project_id, secret_key=token, url=url, proxies=proxies, platform_name=platform_name, provider_name=provider_name, ) get_logger().info(f"Create Scaleway Session", channel.general) def build_remote_processor(self) -> RemoteProcessor: return RemoteProcessor(rpc_handler=self._rpc_handler) def start(self) -> None: self._rpc_handler.create_session( max_duration_s=self._max_idle_duration_s, max_idle_duration_s=self._max_idle_duration_s, deduplication_id=self._deduplication_id, ) def stop(self) -> None: self._rpc_handler.terminate_session() get_logger().info("Stop Scaleway Session", channel.general) def delete(self) -> None: self._rpc_handler.delete_session() get_logger().info( "Stop (if not already) and revoke Scaleway Session", channel.general ) ================================================ FILE: perceval/rendering/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .pdisplay import pdisplay, pdisplay_to_file from .circuit import DisplayConfig, DebugSkin, PhysSkin, SymbSkin from .format import Format ================================================ FILE: perceval/rendering/_density_matrix_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from matplotlib import colormaps from cmath import phase, pi import numpy as np from numpy.linalg import norm import math def _complex_to_rgb(z: complex, cmap='hsv'): """for better rendering, cmap should be a cyclic matplotlib ColorMap""" r, g, b, a = colormaps[cmap]((phase(z) + pi) / (2 * pi)) a = abs(z) vect = np.array([r, g, b]) vect = (a / norm(vect)) * vect return vect def _csr_to_rgb(matrix, cmap='hsv'): """convert a complex csr_matrix to an rgb image""" if matrix.ndim != 2: raise ValueError(f"matrix should be a 2d array, not {matrix.ndim}d") img = np.zeros(matrix.shape + (3,)) coef_max = 0 for i in range(matrix.shape[0]): for j in range(matrix.shape[0]): z = matrix[i, j] if z != 0: img[i, j, :] = _complex_to_rgb(z, cmap) if abs(z) > coef_max: coef_max = abs(z) img = (1/coef_max) * img return img def _csr_to_greyscale(matrix): """convert a complex matrix to a greyscale image""" if matrix.ndim != 2: raise ValueError(f"matrix should be a 2d array, not {matrix.ndim}d") img = np.zeros(matrix.shape) coef_max = 0 for i in range(matrix.shape[0]): for j in range(matrix.shape[0]): z = matrix[i, j] if z != 0: img[i, j] = 255*abs(z) if abs(z) > coef_max: coef_max = abs(z) img = (1 / coef_max) * img return img def generate_ticks(dm): m, n = dm.m, dm.n_max tick_list = [0] tick_labels = ["0 photon"] for k in range(n): tick_list.append(math.comb(m+k, k)) tick_labels.append(str(k+1)+" photons") return tick_list, tick_labels ================================================ FILE: perceval/rendering/_processor_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components import PERM, Experiment, Barrier class ComponentHeraldInfo: """ Store, for a component, indices of the heralds attached to its inputs or outputs """ def __init__(self): self.input_heralds = {} self.output_heralds = {} def register_herald(self, forward_pass, mode_index, herald_mode): if forward_pass: self.input_heralds[mode_index] = herald_mode else: self.output_heralds[mode_index] = herald_mode def collect_herald_info(processor: Experiment, recursive: bool): """ Return a dictionary mapping a component to a HeraldInfo object. The HeraldInfo object stores information about which inputs and outputs are connected to herald modes. For example, if d[my_component].output_heralds[0] == 5, then output mode 0 of the component is the herald at final mode 5. For correct rendering, the information should be collected at the same level of granularity as the drawing: for individual circuit elements when recursive is True, for blocks when recursive is False. """ if recursive: component_list = processor.flatten(max_depth=1) else: component_list = processor.components herald_info = {} forward_pass = True for heralds in (processor.in_heralds, processor.heralds): for herald_mode in heralds.keys(): # Do one forward pass to identify heralds on inputs, and one # backward pass to identify heralds "plugged" on outputs c_list = component_list if forward_pass else component_list[::-1] mode = herald_mode for mode_range, component in c_list: m0 = mode_range[0] if mode not in mode_range: continue if isinstance(component, PERM): # Heralds can be moved across permutations if forward_pass: mode = component.perm_vector[mode - m0] + m0 else: mode = component.perm_vector.index(mode - m0) + m0 elif isinstance(component, Barrier): pass # Heralds can go through barriers too else: h_info = herald_info.setdefault( component, ComponentHeraldInfo()) h_info.register_herald(forward_pass, mode - m0, herald_mode) break forward_pass = False return herald_info ================================================ FILE: perceval/rendering/canvas/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .canvas import Canvas ================================================ FILE: perceval/rendering/canvas/canvas.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import ABC class Canvas(ABC): def __init__(self, inverse_Y: bool = False, **opts): self._position = [] self._minx = None self._miny = None self._maxx = None self._maxy = None self._drawn = False self._offset_x = 0 self._offset_y = 0 self._opts = opts self._inverse_Y = -1 if inverse_Y else 1 self._background_color = None def set_offset(self, v: tuple[float, float], width: float, height: float): self._offset_x = v[0] self._offset_y = v[1] self.position = (0, 0) self.position = (width, height) @property def position(self): return self._position @property def relative_position(self): return self._position[0]-self._offset_x, self._position[1]-self._offset_y @position.setter def position(self, v): (x, y) = (v[0]+self._offset_x, v[1]+self._offset_y) if self._minx is None or x < self._minx: self._minx = x if self._miny is None or y < self._miny: self._miny = y if self._maxx is None or x > self._maxx: self._maxx = x if self._maxy is None or y > self._maxy: self._maxy = y self._position = (x, y) def height(self): return self._maxy - self._miny def width(self): return self._maxx - self._minx def add_mline(self, points: list[float], stroke: str = "black", stroke_width: float = 1, stroke_linejoin: str = "miter", stroke_dasharray=None): """Draw a multi-line :param points: list of point 2D coordinates :param stroke: Stroke color :param stroke_width: Width of the drawn multi-line :param stroke_linejoin: Shape to join two segments of the multi-line :param stroke_dasharray: Dash pattern of the multi-line """ assert not self._drawn, "calling add_mline on drawn canvas" norm_points = [] for x, y in [points[n:n+2] for n in range(0, len(points), 2)]: self.position = (x, y) norm_points += [self.position[0], self._inverse_Y * self.position[1]] return norm_points def add_polygon(self, points: list[float], stroke: str = "black", stroke_width: float = 1, fill: str = None, stroke_linejoin: str = "miter", stroke_dasharray=None, inverse=True): """Draw a polygon :param fill: :param points: :param stroke: :param stroke_width: :return: """ assert not self._drawn, "calling add_polygon on drawn canvas" norm_points = [] for x, y in [points[n:n+2] for n in range(0, len(points), 2)]: self.position = (x, y) norm_points += [self.position[0], self._inverse_Y * self.position[1]] return norm_points def add_rect(self, points: tuple[float, float], width: float, height: float, **args): self.add_polygon([points[0], points[1], points[0]+width, points[1], points[0]+width, points[1]+height, points[0], points[1]+height], **args) def add_mpath(self, points: list[float | str] | tuple[float | str, ...], stroke: str = "black", stroke_width: float = 1, fill: str = None, stroke_linejoin: str = "miter", stroke_dasharray=None): """Draw a path :param points: list of point 2D coordinates :param stroke: Stroke color :param stroke_width: Width of the drawn multi-line :param fill: Fill color :param stroke_linejoin: Shape to join two segments of the multi-line :param stroke_dasharray: Dash pattern of the multi-line """ assert not self._drawn, "calling add_mpath on drawn canvas" norm_points = [] r_position_start = None while points: if points[0] == "z": self.position = r_position_start norm_points += ["L", self.position[0], self._inverse_Y * self.position[1]] points = points[1:] elif points[0] == "M": self.position = points[1:3] if r_position_start is None: r_position_start = self.relative_position norm_points += ["M", self.position[0], self._inverse_Y * self.position[1]] points = points[3:] elif points[0] == "m": self.position = (self.relative_position[0]+points[1], self.relative_position[1]+points[2]) norm_points += ["M", self.position[0], self._inverse_Y * self.position[1]] points = points[3:] elif points[0] == "L": self.position = points[1:3] norm_points += ["L", self.position[0], self._inverse_Y * self.position[1]] points = points[3:] elif points[0] == "l": self.position = (self.relative_position[0]+points[1], self.relative_position[1]+points[2]) norm_points += ["L", self.position[0], self._inverse_Y * self.position[1]] points = points[3:] elif points[0] == "C": self.position = points[1:3] norm_points += ["C", self.position[0], self._inverse_Y * self.position[1]] self.position = points[3:5] norm_points += [self.position[0], self._inverse_Y * self.position[1]] self.position = points[5:7] norm_points += [self.position[0], self._inverse_Y * self.position[1]] points = points[7:] elif points[0] == "c": position_begin = self.relative_position self.position = (position_begin[0]+points[1], position_begin[1]+points[2]) norm_points += ["C", self.position[0], self._inverse_Y * self.position[1]] self.position = (position_begin[0]+points[3], position_begin[1]+points[4]) norm_points += [self.position[0], self._inverse_Y * self.position[1]] self.position = (position_begin[0]+points[5], position_begin[1]+points[6]) norm_points += [self.position[0], self._inverse_Y * self.position[1]] points = points[7:] elif points[0] == "S": self.position = points[1:3] norm_points += ["S", self.position[0], self._inverse_Y * self.position[1]] self.position = points[3:5] norm_points += [self.position[0], self._inverse_Y * self.position[1]] points = points[5:] elif points[0] == "s": position_begin = self.relative_position self.position = (position_begin[0]+points[1], position_begin[1]+points[2]) norm_points += ["S", self.position[0], self._inverse_Y * self.position[1]] self.position = (position_begin[0]+points[3], position_begin[1]+points[4]) norm_points += [self.position[0], self._inverse_Y * self.position[1]] points = points[5:] elif points[0] == "H": self.position = (points[1], self.position[1]) norm_points += ["L", self.position[0], self._inverse_Y * self.position[1]] points = points[2:] elif points[0] == "h": self.position = (points[1] + self.relative_position[0], self.relative_position[1]) norm_points += ["L", self.position[0], self._inverse_Y * self.position[1]] points = points[2:] elif points[0] == "V": self.position = (self.position[0], points[1]) norm_points += ["L", self.position[0], self._inverse_Y * self.position[1]] points = points[2:] elif points[0] == "v": self.position = (self.relative_position[0], points[1] + self.relative_position[1]) norm_points += ["L", self.position[0], self._inverse_Y * self.position[1]] points = points[2:] else: raise RuntimeError(f"Unsupported mpath operator: {points[0]}") return norm_points def add_circle(self, points: tuple[float, float], r: float, stroke: str = "black", stroke_width: float = 1, fill: str = None, stroke_dasharray = None): """ Draw a circle :param r: Radius :param points: list of point 2D coordinates :param stroke: Stroke color :param stroke_width: Width of the drawn circle :param fill: Fill color :param stroke_dasharray: Dash pattern of the circle """ self.position = (points[0] + r, points[1] + r) self.position = (points[0] - r, points[1] - r) self.position = points return self.position[0], self._inverse_Y * self.position[1] def add_text(self, points: tuple[float, float], text: str, size: float, ta: str = "left", # Literal["left", "middle", "right"] fontstyle: str = "normal" # Literal["normal", "bold", "italic"] ): self.position = points f_points = self.position if ta == "left": self.position = (points[0]+size*len(text), points[1]+size) elif ta == "right": self.position = (points[0]-size*len(text), points[1]+size) else: self.position = (points[0]-size*len(text)/2, points[1]+size) self.position = (points[0]+size*len(text)/2, points[1]+size) return f_points[0], self._inverse_Y * f_points[1] def normalize_text(self, text: str, size: float, points: tuple[float, float], max_size: int, shift_factor: float) -> tuple[str, int, tuple[float, float]]: """ If the text does not fit in max_size, reduce the font size or split text over several lines. :param text: the text to check :param size: the font size of the text :param points: the position on the canvas. The text can be moved vertically when lines are added. :param max_size: the maximum size that the text can take horizontally :param shift_factor: to move the text vertically when adding lines :return: Modified text, size and points. """ if max_size is not None and size * len(text) > max_size: font_size_factor = 1.5 new_size = int(max_size / len(text) * font_size_factor) if new_size < size / 2: size = size / 2 max_len = max_size/size * font_size_factor # try to split text on spaces instead of reducing size pos_x, pos_y = points remaining = text new_text = '' min_index = int(max_len * 0.8) max_index = int(max_len * 1.2) while size * len(remaining) > max_size: index = int(max_len) if ' ' in remaining[min_index:max_index]: index = remaining[min_index:max_index].index(' ') + min_index new_text += remaining[:index] + '\n' remaining = remaining[index:] pos_y += shift_factor*size new_text += remaining text = new_text points = (pos_x, pos_y) else: size = new_size return text, size, points def add_shape(self, shape_fn, circuit, mode_style): shape_fn(circuit, self, mode_style) def set_background_color(self, background_color): """ The canvas is not expected to change its background color in response to this, but a drawable element can retrieve this property to know whether it is drawn on a white or a colored surface. """ self._background_color = background_color @property def background_color(self): return self._background_color def draw(self): assert not self._drawn, "calling draw on drawn canvas" self._drawn = True ================================================ FILE: perceval/rendering/canvas/latex_canvas.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .canvas import Canvas from copy import copy import latexcodec # noqa tikz_implemented_colors = { "aquamarine": "\\definecolor{aquamarine}{rgb}{0.5, 1.0, 0.83}", "black": True, "blue": True, "brown": True, "cyan": True, "darkgray": True, "darkred": "\\definecolor{darkred}{rgb}{0.55, 0.0, 0.0}", "dimgrey": "\\definecolor{dimgrey}{rgb}{0.27, 0.27, 0.27}", "gray": True, "green": True, "honeydew": "\\definecolor{honeydew}{rgb}{0.94, 1, 0.94}", "lightblue": "\\definecolor{lightblue}{rgb}{0.68, 0.85, 0.9}", "lightgray": True, "lightgreen": "\\definecolor{lightgreen}{rgb}{0.56, 0.93, 0.56}", "lightpink": "\\definecolor{lightpink}{rgb}{1.0, 0.71, 0.76}", "lightyellow": "\\definecolor{lightyellow}{rgb}{1.0, 1.0, 0.88}", "lime": True, "magenta": True, "olive": True, "orange": True, "pink": True, "purple": True, "red": True, "teal": True, "thistle": "\\definecolor{thistle}{rgb}{0.85, 0.75, 0.85}", "violet": True, "white": True, "whitesmoke": "\\definecolor{whitesmoke}{rgb}{0.96, 0.96, 0.96}", "yellow": True, } class LatexCanvas(Canvas): def __init__(self, **opts): super().__init__(**opts) self._header = [ "\\documentclass{standalone}", "\\usepackage{tikz}", ] self._prescript = [ "\\begin{document}", "\\begin{tikzpicture}[scale=1.0,x=1pt,y=1pt]", ] self._draws = [] self._postscript = [ "\\end{tikzpicture}", "\\end{document}", ] self._color_check_list = tikz_implemented_colors.copy() def _check_color_is_implemented(self, color): if color not in self._color_check_list: raise NotImplementedError(f"Color {color} is not defined") elif self._color_check_list[color] is not True: self._header.append(self._color_check_list[color]) self._color_check_list[color] = True def add_mline( self, points, stroke="black", stroke_width=1, stroke_linejoin="miter", stroke_dasharray=None, ): points = super().add_mline(points, stroke, stroke_width) self._check_color_is_implemented(stroke) nodes = [] for idx in range(0, len(points), 2): nodes.append(f"({points[idx]},{-points[idx+1]})") self._draws.append( f"\\draw[color={stroke},line width={stroke_width}] " + " -- ".join(nodes) + ";" ) def add_polygon( self, points, stroke="black", stroke_width=1, fill=None, stroke_linejoin="miter", stroke_dasharray=None, ): points = super().add_polygon(points, stroke, stroke_width, fill) self._check_color_is_implemented(stroke) if fill is None: fill = "none" else: self._check_color_is_implemented(fill) nodes = [] for idx in range(0, len(points), 2): nodes.append(f"({points[idx]},{-points[idx+1]})") self._draws.append( f"\\draw[color={stroke},line width={stroke_width},fill={fill}] " + " -- ".join(nodes) + " -- cycle;" ) def add_mpath( self, points, stroke="black", stroke_width=1, fill=None, stroke_linejoin="miter", stroke_dasharray=None, ): points = super().add_mpath(points, stroke, stroke_width, fill) self._check_color_is_implemented(stroke) if fill is None: fill = "none" else: self._check_color_is_implemented(fill) pathstr = f"\\draw[color={stroke},line width={stroke_width},line join={stroke_linejoin},fill={fill}]" idx = 0 x_pos = y_pos = x_ctl_1 = y_ctl_1 = x_ctl_2 = y_ctl_2 = x_end = y_end = 0 while idx < len(points): if points[idx] == "M": x_pos, y_pos = points[idx + 1 : idx + 3] idx += 2 elif points[idx] == "L": x_prev, y_prev = copy(x_end), copy(y_end) x_end, y_end = points[idx + 1 : idx + 3] if x_prev != x_pos and y_prev != y_pos: pathstr += f" ({x_pos},{-y_pos}) -- ({x_end},{-y_end})" else: pathstr += f" -- ({x_end},{-y_end})" x_pos, y_pos = x_end, y_end idx += 2 elif points[idx] == "S": x_prev, y_prev = copy(x_end), copy(y_end) x_ctl_1, y_ctl_1 = x_ctl_2, y_ctl_2 x_ctl_2, y_ctl_2, x_end, y_end = points[idx + 1 : idx + 5] if x_prev != x_pos and y_prev != y_pos: pathstr += f" ({x_pos},{-y_pos}) .. controls ({x_ctl_1},{-y_ctl_1}) and ({x_ctl_2},{-y_ctl_2}) .. ({x_end},{-y_end})" else: pathstr += f" .. controls ({x_ctl_1},{-y_ctl_1}) and ({x_ctl_2},{-y_ctl_2}) .. ({x_end},{-y_end})" x_pos, y_pos = x_end, y_end idx += 4 elif points[idx] == "C": x_prev, y_prev = copy(x_end), copy(y_end) x_ctl_1, y_ctl_1, x_ctl_2, y_ctl_2, x_end, y_end = points[ idx + 1 : idx + 7 ] if x_prev != x_pos and y_prev != y_pos: pathstr += f" ({x_pos},{-y_pos}) .. controls ({x_ctl_1},{-y_ctl_1}) and ({x_ctl_2},{-y_ctl_2}) .. ({x_end},{-y_end})" else: pathstr += f" .. controls ({x_ctl_1},{-y_ctl_1}) and ({x_ctl_2},{-y_ctl_2}) .. ({x_end},{-y_end})" x_pos, y_pos = x_end, y_end idx += 6 idx += 1 self._draws.append(pathstr + ";") def add_circle( self, points, r, stroke="black", stroke_width=1, fill=None, stroke_dasharray=None, ): points = super().add_circle(points, r, stroke, stroke_width, fill) if fill is None: fill = "none" self._draws.append( f"\\draw[color={stroke},line width={stroke_width},fill={fill}] ({points[0]},{points[1]}) circle[radius={r}];" ) def add_text(self, points, text, size, ta="left", fontstyle="normal", max_size=None): if max_size is not None: text, size, points = self.normalize_text(text, size, points, max_size, 0.5) if ta == "middle": ta = "base" elif ta == "left": ta = "west" elif ta == "right": ta = "east" text = text.replace("\n", "\\\\ ") text = text.encode("latex").decode("utf-8") points = super().add_text(points, text, size, ta) if fontstyle == "normal": self._draws.append( f"\\node[align=center,anchor={ta},font = {{\\fontsize{{{size}pt}}{{0}}\\selectfont}}] at ({points[0]},{-points[1]}) {{{text}}};" ) elif fontstyle == "italic": self._draws.append( f"\\node[align=center,anchor={ta},font = {{\\fontsize{{{size}pt}}{{0}}\\selectfont\\itshape}}] at ({points[0]},{-points[1]}) {{{text}}};" ) elif fontstyle == "bold": self._draws.append( f"\\node[align=center,anchor={ta},font = {{\\fontsize{{{size}pt}}{{0}}\\selectfont\\bfseries}}] at ({points[0]},{-points[1]}) {{{text}}};" ) else: raise NotImplementedError(f"Font style {fontstyle} not implemented") def draw(self): super().draw() return "\n".join( self._header + self._prescript + self._draws + self._postscript ) ================================================ FILE: perceval/rendering/canvas/mplot_canvas.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .canvas import Canvas from ..mplotlib_renderers._mplot_utils import autoselect_backend autoselect_backend() import matplotlib.pyplot as plt import matplotlib.path as mpath import matplotlib.patches as mpatches from matplotlib.collections import PatchCollection class MplotCanvas(Canvas): def __init__(self, **opts): super().__init__(**opts, inverse_Y=True) self._fig, self._ax = plt.subplots() if "total_width" in opts: self._fig.set_figwidth((opts["total_width"]+1)*2) self._fig.set_figheight((opts["total_height"]+1)*2) self._patches = [] def add_mline(self, points, stroke="black", stroke_width=1, stroke_linejoin="miter", stroke_dasharray=None): mpath = ["M", points[0], points[1]] for n in range(2, len(points), 2): mpath += ["L", points[n], points[n+1]] self.add_mpath(mpath, stroke=stroke, stroke_width=stroke_width, stroke_linejoin=stroke_linejoin, stroke_dasharray=stroke_dasharray) def add_polygon(self, points, stroke="black", stroke_width=1, fill=None, stroke_linejoin="miter", stroke_dasharray=None): points = super().add_polygon(points, stroke, stroke_width, fill) self._patches.append( mpatches.Polygon([(points[n], points[n+1]) for n in range(0, len(points), 2)], fill=fill is not None, color=fill, ec=stroke, linewidth=stroke_width, joinstyle=stroke_linejoin)) def add_mpath(self, points, stroke="black", stroke_width=1, fill=None, stroke_linejoin="miter", stroke_dasharray=None): points = super().add_mpath(points, stroke, stroke_width, fill) path_data = [] while points: if points[0] == "M": path_data.append((mpath.Path.MOVETO, [points[1], points[2]])) points = points[3:] elif points[0] == "L": path_data.append((mpath.Path.LINETO, [points[1], points[2]])) points = points[3:] elif points[0] == "C": path_data.append((mpath.Path.CURVE4, [points[1], points[2]])) path_data.append((mpath.Path.CURVE4, [points[3], points[4]])) path_data.append((mpath.Path.CURVE4, [points[5], points[6]])) points = points[7:] elif points[0] == "S": path_data.append((mpath.Path.CURVE3, [points[1], points[2]])) path_data.append((mpath.Path.CURVE3, [points[3], points[4]])) points = points[5:] codes, vertices = zip(*path_data) path = mpath.Path(vertices, codes) linestyle = '-' if stroke_dasharray is None else '--' self._patches.append(mpatches.PathPatch(path, fill=fill is not None, color=fill, ec=stroke, linewidth=stroke_width, joinstyle=stroke_linejoin, linestyle=linestyle)) def add_circle(self, points, r, stroke="black", stroke_width=1, fill=None, stroke_dasharray=None): points = super().add_circle(points, r, stroke, stroke_width, fill) self._patches.append(mpatches.Circle((points[0], points[1]), r, fill=fill is not None, color=fill, ec=stroke, linewidth=stroke_width)) def add_text(self, points, text, size, ta="left", fontstyle="normal", max_size=None): if max_size is not None: text, size, points = self.normalize_text(text, size, points, max_size, 0.5) points = super().add_text(points, text, size, ta) if ta == "middle": ta = "center" kwargs = { 'ha': ta, 'size': size*3 } if fontstyle == "italic": kwargs['fontstyle'] = fontstyle elif fontstyle == "bold": kwargs['fontweight'] = fontstyle plt.text(points[0], points[1], text, **kwargs) def draw(self): super().draw() collection = PatchCollection(self._patches, match_original=True) self._ax.add_collection(collection) plt.axis('equal') plt.axis('off') plt.tight_layout() if "mplot_savefig" in self._opts: plt.savefig(self._opts["mplot_savefig"]) if "mplot_noshow" not in self._opts or not self._opts["mplot_noshow"]: plt.show() plt.close(self._fig) return self ================================================ FILE: perceval/rendering/canvas/svg_canvas.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from ..drawsvg_wrapper import DrawsvgWrapper from .canvas import Canvas import warnings with warnings.catch_warnings(): warnings.filterwarnings( action='ignore', category=RuntimeWarning) import drawsvg as draw class SvgCanvas(Canvas): """ This class relies on drawsvg 3rd party library. With it, it is possible to create dynamic svg graphics. """ def __init__(self, **opts): super().__init__(**opts) self._draws = [] self._render_height = (opts["total_height"]+.5)*50 self._pixel_size = 1.25 if 'render_size' in opts: self._pixel_size *= opts['render_size'] if 'group' in opts: self._group = opts['group'] def add_mline(self, points, stroke="black", stroke_width=1, stroke_linejoin="miter", stroke_dasharray=None): points = super().add_mline(points, stroke, stroke_width) self._draws.append(draw.Lines(*points, stroke=stroke, stroke_width=stroke_width, fill="none", close=False, stroke_dasharray=stroke_dasharray)) def add_polygon(self, points, stroke="black", stroke_width=1, fill=None, stroke_linejoin="miter", stroke_dasharray=None): points = super().add_polygon(points, stroke, stroke_width, fill) if fill is None: fill = "none" self._draws.append(draw.Lines(*points, stroke=stroke, fill=fill, close=True, stroke_dasharray=stroke_dasharray, stroke_linejoin=stroke_linejoin)) def add_mpath(self, points, stroke="black", stroke_width=1, fill=None, stroke_linejoin="miter", stroke_dasharray=None): points = super().add_mpath(points, stroke, stroke_width, fill) if fill is None: fill = "none" p = draw.Path(stroke_width=stroke_width, stroke=stroke, stroke_linejoin=stroke_linejoin, fill=fill) idx = 0 while idx < len(points): if points[idx] == 'M': p.M(*points[idx+1:idx+3]) idx += 2 elif points[idx] == 'L': p.L(*points[idx + 1:idx + 3]) idx += 2 elif points[idx] == 'S': p.S(*points[idx + 1:idx + 5]) idx += 4 elif points[idx] == 'C': p.C(*points[idx+1:idx+7]) idx += 6 idx += 1 self._draws.append(p) def add_circle(self, points, r, stroke="black", stroke_width=1, fill=None, stroke_dasharray=None): points = super().add_circle(points, r, stroke, stroke_width, fill) if fill is None: fill = "none" self._draws.append(draw.Circle(points[0], points[1], r, stroke_width=stroke_width, fill=fill, stroke=stroke)) def add_text(self, points, text, size, ta="start", fontstyle="normal", max_size=None): if ta == "right": ta = "end" elif ta == "left": ta = "start" if max_size is not None: text, size, points = self.normalize_text(text, size, points, max_size, -0.5) points = super().add_text(points, text, size, ta) opts = {'text_anchor': ta} if fontstyle == "italic": opts['font_style'] = "italic" elif fontstyle == "bold": opts['font_weight'] = "bold" self._draws.append(draw.Text(text, size, *points, **opts)) def draw(self): super().draw() if hasattr(self, "_group"): d = draw.Group(x=self._group[0], y=self._group[1]) else: d = draw.Drawing(self.width()+50, self._render_height, origin=(self._minx-25, 0)) for dr in self._draws: d.append(dr) return DrawsvgWrapper(d.set_pixel_scale(self._pixel_size)) ================================================ FILE: perceval/rendering/circuit/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .abstract_skin import ASkin from .phys_skin import PhysSkin from .symb_skin import SymbSkin from .debug_skin import DebugSkin from .create_renderer import RendererFactory from .display_config import DisplayConfig ================================================ FILE: perceval/rendering/circuit/_canvas_shapes.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. class ShapeFactory: pr_mpath = ["M", 18, 27, "c", 0.107, 0.131, 0.280, 0.131, 0.387, 0, "l", 2.305, -2.821, "c", 0.107, -0.131, 0.057, -0.237, -0.112, -0.237, "h", -1.22, "c", -0.169, 0, -0.284, -0.135, -0.247, -0.300, "c", 0.629, -2.866, 3.187, -5.018, 6.240, -5.018, "c", 3.524, 0, 6.39, 2.867, 6.390, 6.3902, "c", 0, 3.523, -2.866, 6.39, -6.390, 6.390, "c", -0.422, 0, -0.765, 0.342, -0.765, 0.765, "s", 0.342, 0.765, 0.765, 0.765, "c", 4.367, 0, 7.92, -3.552, 7.920, -7.920, "c", 0, -4.367, -3.552, -7.920, -7.920, -7.920, "c", -3.898, 0, -7.146, 2.832, -7.799, 6.546, "c", -0.029, 0.166, -0.184, 0.302, -0.353, 0.302, "h", -1.201, "c", -0.169, 0, -0.219, 0.106, -0.112, 0.237, "z"] @staticmethod def half_circle_port_in(radius: float): return ["M", 7, 25, "c", 0, 0, 0, -radius, radius, -radius, "h", 8, "v", 2 * radius, "h", -8, "c", -radius, 0, -radius, -radius, -radius, -radius, "z"] @staticmethod def half_circle_port_out(radius: float, x_offset: float = 8): return ["M", x_offset, 35, "h", -8, "v", -2 * radius, "h", 8, "c", 0, 0, radius, 0, radius, radius, "c", 0, radius, -radius, radius, -radius, radius, "z"] @staticmethod def triangle_port_out(size: float, x_offset: float = 0): return ["M", x_offset, 25 + size, "L", 18 + x_offset, 25, "L", x_offset, 25 - size, "z"] @staticmethod def polygon_port_out(size: float, x_offset: float = 0): return ["M", x_offset, 25 + size, "L", 10 + x_offset, 25 + size*.85, "L", 18 + x_offset, 25, "L", 10 + x_offset, 25-size*.85, "L", x_offset, 25-size, "z"] @staticmethod def bs_symbolic_mpath(compact: bool): coeff = 1 if compact else 2 return ["M", 6.4721*coeff, 25, "c", 6.8548*coeff, 0, 6.8241*coeff, 25, 13.68*coeff, 25, "m", 0.0009*coeff, 0, "c", -6.8558*coeff, 0, -6.825*coeff, 25, -13.68*coeff, 25, "m", 13.68*coeff, -25, "h", 10.9423*coeff, "m", 0, 0, "c", 6.8558*coeff, 0, 6.825*coeff, -25, 13.68*coeff, -25, "m", -13.6799*coeff, 25, "c", 6.8558*coeff, 0, 6.825*coeff, 25, 13.68*coeff, 25, "m", -44.7741*coeff, -50, "h", 6.5*coeff, "m", 0.0009*coeff, 50, "h", -6.5009*coeff, "m", 43.8227*coeff, 0, "h", 6.1773*coeff, "m", -6.4028*coeff, -50, "h", 6.4028*coeff] @staticmethod def rounded_corner_square(radius: float, length_ratio: float): return ["M", 0, radius, "c", 0, 0, 0, -radius, radius, -radius, "l", length_ratio * radius, 0, "c", radius, 0, radius, radius, radius, radius, "l", 0, length_ratio * radius, "c", 0, 0, 0, radius, -radius, radius, "l", -length_ratio * radius, 0, "c", -radius, 0, -radius, -radius, -radius, -radius, "l", 0, -length_ratio * radius, "z"] ================================================ FILE: perceval/rendering/circuit/abstract_skin.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import ABC, abstractmethod from multipledispatch import dispatch from perceval import Experiment from perceval.components import ACircuit, AFFConfigurator, PERM, AComponent, TD, LC, CompiledCircuit from perceval.utils import format_parameters, ModeType class ASkin(ABC): """ A skin is required in the use of pdisplay for the following formats: * Format.HTML * Format.MPLOT * Format.LATEX A skin has three major responsibilities: * measuring the display size of a component / composite circuit * providing shape functions to draw individual components * exposing style data (stroke style, colors, etc.) :param photonic_style: photonic mode style, containing stroke specifications {"stroke": color_str, "stroke_width": int} :param style_subcircuit: subcircuit style specifications {"width": int, "stroke_style": {"stroke": color_str, "stroke_width": int}, "fill": color_str} :param compact_display: whether to display some large components in a more compact way, to use less screen space """ def __init__(self, photonic_style: dict, style_subcircuit: dict, compact_display: bool = False): self._compact = compact_display self.style = {ModeType.PHOTONIC: photonic_style, ModeType.HERALD: {"stroke": None, "stroke_width": 1}, ModeType.CLASSICAL: {"stroke": "blue", "stroke_width": 1} } self.style_subcircuit = style_subcircuit self.precision: float = 1e-6 self.nsimplify: bool = True @dispatch((ACircuit, TD, LC), bool) def get_size(self, c: ACircuit, recursive: bool = False) -> tuple[int, int]: """Gets the size of a circuit in arbitrary unit. If composite, it will take its components into account""" if not c.is_composite(): return self.measure(c) # w represents the graph of the circuit. # Each value being the output of the rightmost component on the corresponding mode w = [0] * c.m for modes, comp in c._components: r = slice(modes[0], modes[0]+comp.m) start_w = max(w[r]) if comp.is_composite() and recursive: comp_width, _ = self.get_size(comp, False) else: comp_width = self.get_width(comp) end_w = start_w + comp_width w[r] = [end_w] * comp.m return max(w), c.m @dispatch(Experiment, bool) def get_size(self, p: Experiment, recursive: bool = False) -> tuple[int, int]: height = p.m # w represents the graph of the circuit. # Each value being the output of the rightmost component on the corresponding mode w = [0] * p.circuit_size for modes, comp in p.components: if not isinstance(comp, PERM): height = max(height, comp.m + modes[0]) r = slice(modes[0], modes[0] + comp.m) start_w = max(w[r]) if comp.is_composite() and recursive: if isinstance(comp, CompiledCircuit) and comp.template is not None: comp = comp.template comp_width, _ = self.get_size(comp, False) elif isinstance(comp, AFFConfigurator) and recursive: comp_width, _ = self.get_size(comp.circuit_template(), False) else: comp_width = self.get_width(comp) end_w = start_w + comp_width w[r] = [end_w] * comp.m return max(w) + 1, min(p.circuit_size, height+2) @dispatch(CompiledCircuit, bool) def get_size(self, c: CompiledCircuit, recursive: bool = False) -> tuple[int, int]: """Gets the size of a circuit in arbitrary unit. If composite, it will take its components into account""" if c.template is not None: return self.get_size(c.template, recursive) return c.m, c.m def measure(self, c: AComponent) -> tuple[int, int]: """ Returns the measure (in arbitrary unit (AU) where the space between two modes = 1 AU) of a single component treated as a block (meaning that a composite circuit will not be measured recursively. Use get_size() instead) """ return self.get_width(c), c.m @abstractmethod def get_width(self, c) -> int: """Returns the width of a component :param c: the component to measure """ @abstractmethod def get_shape(self, c) -> callable: """Returns a callable able to draw the shape of a given component on a canvas :param c: the component to draw """ @abstractmethod def default_shape(self, c, canvas, mode_style): """ Define a fallback shape for unknown components :param c: the component to draw :param canvas: the canvas on which to draw :param mode_style: the style for all modes """ def _get_display_content(self, circuit: ACircuit) -> str: return format_parameters(circuit.get_variables(), self.precision, self.nsimplify) ================================================ FILE: perceval/rendering/circuit/canvas_renderer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from copy import copy from .renderer_interface import ICircuitRenderer from ..canvas import Canvas from perceval.components import ACircuit, APort, PortLocation, PERM, IDetector, Herald from perceval.utils import ModeType, BasicState class _PortPos: def __init__(self, x, y): self.x = x self.y = y class CanvasRenderer(ICircuitRenderer): AFFIX_PORT_SIZE = 15 AFFIX_ALL_SIZE = 25 SCALE = 50 def __init__(self, nsize, canvas: Canvas, skin): super().__init__(nsize) # first position available for row n self._chart = [0] * (nsize + 1) self._canvas = canvas self._skin = skin self._canvas.set_offset( (0, 0), CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE * (nsize + 1)) self._n_font_size = min(10, max(6, self._nsize + 1)) self._herald_info = None self._in_port_pos = [] self._out_port_pos = [] for i in range(nsize): self._in_port_pos.append(_PortPos(0, i)) for i in range(nsize): self._out_port_pos.append(_PortPos(0, i)) def open(self): for k in range(self._nsize): mode_style = self._skin.style[self._mode_style[k]] if mode_style['stroke']: self._canvas.add_mpath([ "M", CanvasRenderer.AFFIX_ALL_SIZE - CanvasRenderer.AFFIX_PORT_SIZE, CanvasRenderer.SCALE / 2 + CanvasRenderer.SCALE * k, "l", CanvasRenderer.AFFIX_PORT_SIZE, 0], **mode_style) def get_circuit_size(self, circuit: ACircuit, recursive: bool = False) -> tuple[int, int]: return self._skin.get_size(circuit, recursive) def display_input_photons(self, input_pos: BasicState, mode_style: list[ModeType]) -> None: """ Display half-cup showing the number of expected photons at the beginning of any mode """ for k in range(input_pos.m): if mode_style[k] != ModeType.HERALD: self._canvas.set_offset( (-CanvasRenderer.AFFIX_ALL_SIZE * 1.5, CanvasRenderer.AFFIX_ALL_SIZE * 2 * k), 0, 0) self._canvas.add_mline([ CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE - 2, CanvasRenderer.AFFIX_ALL_SIZE], **self._skin.style[ModeType.PHOTONIC]) h = Herald(input_pos[k]) self._canvas.add_shape(self._skin.get_shape(h, PortLocation.INPUT), h, None) def add_mode_index(self, input_mode_style = None): self._canvas.set_offset( (CanvasRenderer.AFFIX_ALL_SIZE + max(self._chart) * CanvasRenderer.SCALE, 0), CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE * (self._nsize + 1)) for k in range(self._nsize): if self._mode_style[k] != ModeType.HERALD: self._canvas.add_text( (CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE * (k + 0.5) + 3), str(k), self._n_font_size, ta="right") input_mode_style = input_mode_style or self._mode_style self._canvas.set_offset( (0, 0), CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE * (self._nsize + 1)) for k in range(self._nsize): if input_mode_style[k] != ModeType.HERALD: self._canvas.add_text( ( 0, CanvasRenderer.SCALE / 2 + 3 + CanvasRenderer.SCALE * k ), str(k), self._n_font_size, ta="left") def add_out_port(self, n_mode: int, port: APort): max_pos = max(self._chart[0:self._nsize]) h_pos = self._out_port_pos[n_mode].x v_pos = self._out_port_pos[n_mode].y self._canvas.set_offset( ( CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * (h_pos or max_pos), CanvasRenderer.SCALE * v_pos ), CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE) self._canvas.add_shape(self._skin.get_shape(port, PortLocation.OUTPUT), port, None) def add_in_port(self, n_mode: int, port: APort): h_pos = self._in_port_pos[n_mode].x * CanvasRenderer.SCALE v_pos = self._in_port_pos[n_mode].y * CanvasRenderer.SCALE self._canvas.set_offset( (h_pos, v_pos), CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE) self._canvas.add_shape(self._skin.get_shape(port, PortLocation.INPUT), port, None) def add_detectors(self, detector_list: list) -> None: max_pos = max(self._chart[0:self._nsize]) for i, det in enumerate(detector_list): if det is None or self._mode_style[i] != ModeType.PHOTONIC: continue self._canvas.set_offset( ( CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * (max_pos + .5), CanvasRenderer.SCALE * i ), CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE) self._canvas.add_shape(self._skin.get_shape(det), det, [None]) def open_subblock(self, lines: tuple[int, ...], name: str, size: tuple[int, int], color=None): # Get recommended margins for this block margins = self._current_subblock_info.get('margins', (0, 0)) start = lines[0] end = lines[-1] subblock_start = self.max_pos(start, end) area = ( subblock_start, start, size[0] + margins[0] + margins[1], size[1]) self._canvas.set_offset( ( CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * area[0], CanvasRenderer.SCALE * area[1] ), CanvasRenderer.SCALE * area[2], CanvasRenderer.SCALE * area[3]) if color is None: color = "lightblue" self._canvas.set_background_color(color) self._canvas.add_rect( (2, 2), CanvasRenderer.SCALE * area[2] - 4, CanvasRenderer.SCALE * area[3] - 4, fill=color, stroke_dasharray="1,2") self._canvas.add_text( (4, CanvasRenderer.SCALE * (end - start + 1) + 5), name.upper(), 8) # Extend the chart on the left side of the subblock self.extend_pos(start, end, margins[0]) def close_subblock(self, lines: tuple[int, ...]): start = lines[0] end = lines[-1] right_margins = self._current_subblock_info.get('margins', (0, 0))[1] # Extend the chart on the right side of the subblock self.extend_pos(start, end, right_margins) def max_pos(self, start, end, _=None): return max(self._chart[start:end + 1]) def extend_pos(self, start, end, margin: int = 0): maxpos = self.max_pos(start, end) + margin for p in range(start, end + 1): if self._chart[p] != maxpos: self._canvas.set_offset( ( CanvasRenderer.AFFIX_ALL_SIZE + self._chart[p] * CanvasRenderer.SCALE, p * CanvasRenderer.SCALE ), (maxpos - self._chart[p]) * CanvasRenderer.SCALE, CanvasRenderer.SCALE) style = self._skin.style[self._mode_style[p]] if style['stroke']: self._canvas.add_mline( [ 0, CanvasRenderer.SCALE / 2, (maxpos - self._chart[p]) * CanvasRenderer.SCALE, CanvasRenderer.SCALE / 2 ], **style) self._chart[p] = maxpos def _add_shape(self, lines, circuit, w, shape_fn=None): if shape_fn is None: shape_fn = self._skin.get_shape(circuit) start = lines[0] end = lines[-1] self.extend_pos(start, end) max_pos = self.max_pos(start, end) self._canvas.set_offset( ( CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * max_pos, CanvasRenderer.SCALE * start ), CanvasRenderer.SCALE * w, CanvasRenderer.SCALE * (end - start + 1)) modes = self._mode_style[start:(end + 1)] self._canvas.add_shape(shape_fn, circuit, modes) def set_herald_info(self, info): self._herald_info = info def _update_mode_style(self, lines, circuit, w: int): if isinstance(circuit, IDetector): self._mode_style[lines[0]] = ModeType.CLASSICAL elif not isinstance(circuit, PERM): input_heralds = {} output_heralds = {} herald_info = self._herald_info if self._herald_info else {} if circuit in herald_info: output_heralds = herald_info[circuit].output_heralds input_heralds = herald_info[circuit].input_heralds # Position input and output heralds for in_mode, herald_in_mode in input_heralds.items(): self._in_port_pos[herald_in_mode].y = lines[0] + in_mode self._in_port_pos[herald_in_mode].x = self._chart[lines[0] + in_mode] # Start drawing this mode in "photonic" style self._mode_style[lines[0] + in_mode] = ModeType.PHOTONIC for out_mode, herald_out_mode in output_heralds.items(): self._out_port_pos[herald_out_mode].y = lines[0] + out_mode self._out_port_pos[herald_out_mode].x = self._chart[lines[0] + out_mode] + w # Stop drawing this mode (set it in "herald" style) self._mode_style[lines[0] + out_mode] = ModeType.HERALD else: # Permutation case m0 = lines[0] out_modes = copy(self._mode_style) for m_input, m_output in enumerate(circuit.perm_vector): out_modes[m_output + lines[0]] = self._mode_style[m_input + m0] self._mode_style = out_modes def append_circuit(self, lines, circuit): w = self._skin.get_width(circuit) self._add_shape(lines, circuit, w) self._update_mode_style(lines, circuit, w) for i in range(lines[0], lines[-1] + 1): self._chart[i] += w def append_subcircuit(self, lines, circuit): w = self._skin.style_subcircuit['width'] if w: self._add_shape(lines, circuit, w, self._skin.subcircuit_shape) self._update_mode_style(lines, circuit, w) for i in range(lines[0], lines[-1] + 1): self._chart[i] += w def close(self): self.extend_pos(0, self._nsize - 1) max_pos = self.max_pos(0, self._nsize - 1) self._canvas.set_offset( (CanvasRenderer.AFFIX_ALL_SIZE + CanvasRenderer.SCALE * max_pos, 0), CanvasRenderer.AFFIX_ALL_SIZE, CanvasRenderer.SCALE * (self._nsize + 1)) for k in range(self._nsize): mode_style = self._skin.style[self._mode_style[k]] if mode_style['stroke']: self._canvas.add_mpath([ "M", 0, CanvasRenderer.SCALE / 2 + CanvasRenderer.SCALE * k, "l", CanvasRenderer.AFFIX_PORT_SIZE, 0], **mode_style) def draw(self): return self._canvas.draw() class PreRenderer(ICircuitRenderer): """ This performs a dummy rendering pass to keep track of potential layout issues to be fixed in the main rendering. At the moment it is keeping track of the recommended margins for each subblock. """ def __init__(self, nsize, skin): super().__init__(nsize) self._chart = [0] * (nsize + 1) self._herald_info = None self._skin = skin # All these are relative to the subblock currently being rendered self._herald_range = [0, 0] self._subblock_start = 0 def open(self): pass def draw(self): pass def close(self): pass def display_input_photons(self, input_pos) -> None: pass def add_mode_index(self) -> None: pass def add_out_port(self, m: int, port: APort) -> None: pass def add_in_port(self, m: int, content: str) -> None: pass def add_detectors(self, detector_list: list) -> None: pass def set_herald_info(self, info): self._herald_info = info def get_circuit_size(self, circuit: ACircuit, recursive: bool = False): return None def open_subblock(self, lines, name, size, color=None): start = lines[0] end = lines[-1] self._subblock_start = self.max_pos(start, end) self.extend_pos(start, end) self._herald_range = [1 << 32, -1] def close_subblock(self, lines): start = lines[0] end = lines[-1] subblock_end = self.max_pos(start, end) # Add the margin requirements for this subblock self._current_subblock_info['margins'] = ( int(self._herald_range[0] == self._subblock_start), int(self._herald_range[1] == subblock_end)) def max_pos(self, start, end, _=None): return max(self._chart[start:end + 1]) def extend_pos(self, start: int, end: int, margin: int = 0): maxpos = self.max_pos(start, end) + margin for p in range(start, end + 1): self._chart[p] = maxpos def _add_shape(self, lines, circuit, w, shape_fn=None): self.extend_pos(lines[0], lines[-1]) def _update_mode_style(self, lines, circuit, w: int): if not isinstance(circuit, PERM): input_heralds = {} output_heralds = {} herald_info = self._herald_info if self._herald_info else {} if circuit in herald_info: output_heralds = herald_info[circuit].output_heralds input_heralds = herald_info[circuit].input_heralds for out_mode, herald_out_mode in output_heralds.items(): self._herald_range[1] = max( self._herald_range[1], self._chart[lines[0] + out_mode] + w) for in_mode, herald_in_mode in input_heralds.items(): self._herald_range[0] = min( self._herald_range[0], self._chart[lines[0] + in_mode]) def append_circuit(self, lines, circuit): w = self._skin.get_width(circuit) if w: self._add_shape(lines, circuit, w) self._update_mode_style(lines, circuit, w) for i in range(lines[0], lines[-1] + 1): self._chart[i] += w def append_subcircuit(self, lines, circuit): w = self._skin.style_subcircuit['width'] if w: self._add_shape(lines, circuit, w, self._skin.subcircuit_shape) self._update_mode_style(lines, circuit, w) for i in range(lines[0], lines[-1] + 1): self._chart[i] += w ================================================ FILE: perceval/rendering/circuit/create_renderer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.utils.logging import get_logger from ..format import Format from .renderer_interface import ICircuitRenderer from .abstract_skin import ASkin from .canvas_renderer import CanvasRenderer, PreRenderer from .text_renderer import TextRenderer import networkx as nx import importlib class RendererFactory: drawsvg_spec = importlib.util.find_spec("drawsvg") latexcodec_spec = importlib.util.find_spec("latexcodec") matplotlib_spec = importlib.util.find_spec("matplotlib") _CANVAS = { Format.LATEX: None, Format.HTML: None, Format.MPLOT: None, } _TOMOGRAPHY = None _GRAPH = None _DENSITY_MATRIX = None if drawsvg_spec: from ..canvas.svg_canvas import SvgCanvas _CANVAS[Format.HTML] = SvgCanvas if latexcodec_spec: from ..canvas.latex_canvas import LatexCanvas _CANVAS[Format.LATEX] = LatexCanvas if matplotlib_spec: from ..canvas.mplot_canvas import MplotCanvas from ..mplotlib_renderers.density_matrix_renderer import DensityMatrixRenderer from ..mplotlib_renderers.graph_renderer import GraphRenderer from ..mplotlib_renderers.tomography_renderer import TomographyRenderer _CANVAS[Format.MPLOT] = MplotCanvas _TOMOGRAPHY = TomographyRenderer _GRAPH = GraphRenderer _DENSITY_MATRIX = DensityMatrixRenderer @staticmethod def get_circuit_renderer( m: int, # number of modes output_format: Format, # rendering method skin: ASkin, # skin **opts ) -> tuple[ICircuitRenderer, ICircuitRenderer | None]: """ Creates a renderer given the selected format. Dispatches parameters to generated canvas objects A skin object is needed for circuit graphic rendering. This returns a (renderer, pre_renderer) tuple. It is recommended to invoke the pre-renderer on the circuit to correctly pre-compute additional position information that cannot be guessed in a single pass. """ if output_format == Format.TEXT: return TextRenderer(m), None assert skin is not None, "A skin must be selected for circuit rendering" canvaType = RendererFactory._CANVAS[output_format] if not canvaType: get_logger().warn(f"""Missing dependencies to use {output_format}, defaulting to {Format.TEXT} Use `pip install perceval-quandela[rendering]` to install needed packages""") return TextRenderer(m), None canvas = canvaType(**opts) return CanvasRenderer(m, canvas, skin), PreRenderer(m, skin) @staticmethod def get_tomography_renderer( output_format: Format, # rendering method **opts): if output_format == Format.TEXT or output_format == Format.LATEX: raise TypeError(f"Tomography plot does not support {output_format}") if RendererFactory._TOMOGRAPHY: return RendererFactory._TOMOGRAPHY(**opts) raise Exception(f"""Missing dependencies to use {output_format}, defaulting to {Format.TEXT} Use `pip install perceval-quandela[rendering]` to install needed packages""") @staticmethod def get_graph_renderer( output_format: Format, # rendering method **opts): if output_format not in {Format.MPLOT, Format.LATEX}: raise TypeError(f"Graph plot does not support {output_format}") if output_format == Format.MPLOT: return RendererFactory._GRAPH(**opts) class GraphLatexRenderer: def render(g: nx.Graph): return nx.to_latex(g) return GraphLatexRenderer @staticmethod def get_density_matrix_renderer( output_format: Format, # rendering method **opts): if output_format == Format.TEXT or output_format == Format.LATEX: raise TypeError(f"DensityMatrix plot does not support {output_format}") if RendererFactory._DENSITY_MATRIX: return RendererFactory._DENSITY_MATRIX(**opts) raise Exception("""Missing dependencies to use {}, defaulting to {} Use `pip install perceval-quandela[rendering]` to install needed packages""".format(output_format, Format.TEXT)) ================================================ FILE: perceval/rendering/circuit/debug_skin.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components import unitary_components as cp from .phys_skin import PhysSkin, ModeType class DebugSkin(PhysSkin): def __init__(self, compact_display: bool = False): super().__init__(compact_display) self.style[ModeType.PHOTONIC]["stroke_width"] = 8 self.style[ModeType.HERALD] = {"stroke": "orange", "stroke_width": 3} # Display ancillary modes in yellow def ps_shape(self, circuit: cp.PS, canvas, mode_style): canvas.add_mline([0, 25, 50, 25], **self.style[ModeType.PHOTONIC]) fill_color = "gray" if circuit.defined else "red" canvas.add_polygon([5, 40, 14, 40, 28, 10, 19, 10, 5, 40, 14, 40], stroke="black", fill=fill_color, stroke_width=1, stroke_linejoin="miter") canvas.add_text((22, 38), text=self._get_display_content(circuit).replace("phi=", "Φ="), size=7, ta="left") def barrier_shape(self, circuit, canvas, mode_style): m = circuit.m if not circuit.visible: # even if invisible, draw a thin line for debug purpose canvas.add_rect((0, 10), 2, 50 * m - 20, fill="whitesmoke", stroke="whitesmoke") return if canvas.background_color is None: canvas.add_rect((10, 10), 30, 50 * m - 20, fill="whitesmoke", stroke="whitesmoke") for i in range(m): style = self.style[mode_style[i]] if style["stroke"]: canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **style) canvas.add_rect((24, 10), 2, 50 * m - 20, fill="dimgrey", stroke="dimgrey") ================================================ FILE: perceval/rendering/circuit/display_config.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .abstract_skin import ASkin from .phys_skin import PhysSkin from .symb_skin import SymbSkin from .debug_skin import DebugSkin from perceval.utils.persistent_data import PersistentData from perceval.utils import get_logger PDISPLAY_KEY = "pdisplay" SKIN_KEY = "skin" SKIN_MAPPING = { "PhysSkin": PhysSkin, "SymbSkin": SymbSkin, "DebugSkin": DebugSkin } def _get_default_skin(): # Default skin is PhysSkin config = PersistentData().load_config() skin = "PhysSkin" if PDISPLAY_KEY in config: skin = config[PDISPLAY_KEY].get(SKIN_KEY, "PhysSkin") if skin not in SKIN_MAPPING: get_logger().error(f"Invalid skin in persistent configuration {skin}") skin = "PhysSkin" return SKIN_MAPPING[skin] class DisplayConfig: """Handle the display configuration such as: - Skin use for pdisplay. Default skin is the one in the persistent data or, if no config is found, PhysSkin. """ _selected_skin = _get_default_skin() @staticmethod def select_skin(skin: type[ASkin]) -> None: """Select the skin used by pdisplay :param skin: Skin to use for pdisplay """ DisplayConfig._selected_skin = skin @staticmethod def get_selected_skin(**kwargs) -> ASkin: """Get the current selected skin :return: Current selected skin """ return DisplayConfig._selected_skin(**kwargs) @staticmethod def save() -> None: """Save the current Display config in the persistent data """ persistent_data = PersistentData() config = persistent_data.load_config() if PDISPLAY_KEY not in config: config[PDISPLAY_KEY] = {} config[PDISPLAY_KEY][SKIN_KEY] = DisplayConfig._selected_skin.__name__ persistent_data.save_config(config) ================================================ FILE: perceval/rendering/circuit/phys_skin.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math from multipledispatch import dispatch from perceval.components import (AComponent, AFFConfigurator, Circuit, Port, Herald, PortLocation, IDetector, DetectionType, unitary_components as cp, non_unitary_components as nu, CompiledCircuit) from ._canvas_shapes import ShapeFactory from .abstract_skin import ASkin, ModeType from .skin_common import bs_convention_color class PhysSkin(ASkin): _STROKE_WIDTH = 3 def __init__(self, compact_display: bool = False): super().__init__({"stroke": "darkred", "stroke_width": PhysSkin._STROKE_WIDTH}, {"width": 2, "fill": "lightpink", "stroke_style": {"stroke": "darkred", "stroke_width": 1}}, compact_display) self.style[ModeType.CLASSICAL] = {"stroke": "orange", "stroke_width": PhysSkin._STROKE_WIDTH} @dispatch(AComponent) def get_width(self, c) -> int: """Absolute fallback""" return 1 @dispatch((cp.Unitary, CompiledCircuit)) def get_width(self, c) -> int: return c.m @dispatch((Circuit, cp.BS, cp.PBS)) def get_width(self, c) -> int: return 2 @dispatch(AFFConfigurator) def get_width(self, c) -> int: return self.get_width(c.circuit_template()) @dispatch((cp.PS, nu.TD, cp.PERM, cp.WP, cp.PR, nu.LC)) def get_width(self, c) -> int: return 1 @dispatch(cp.Barrier) def get_width(self, c) -> int: return 1 if c.visible else 0 @dispatch(AComponent) def get_shape(self, c): return self.default_shape @dispatch(cp.BS) def get_shape(self, c): return self.bs_shape @dispatch(cp.PS) def get_shape(self, c): return self.ps_shape @dispatch(cp.PBS) def get_shape(self, c): return self.pbs_shape @dispatch(nu.TD) def get_shape(self, c): return self.td_shape @dispatch(cp.Unitary) def get_shape(self, c): return self.unitary_shape @dispatch(cp.PERM) def get_shape(self, c): return self.perm_shape @dispatch((cp.WP, cp.HWP, cp.QWP)) def get_shape(self, c): return self.wp_shape @dispatch(cp.PR) def get_shape(self, c): return self.pr_shape @dispatch(cp.Barrier) def get_shape(self, c): return self.barrier_shape @dispatch(nu.LC) def get_shape(self, c): return self.lc_shape @dispatch(AFFConfigurator) def get_shape(self, c): return self.ffconf_shape @dispatch(CompiledCircuit) def get_shape(self, c): return self.comp_circuit_shape @dispatch(Port, PortLocation) def get_shape(self, port, location): if location == PortLocation.INPUT: return self.port_shape_in return self.port_shape_out @dispatch(Herald, PortLocation) def get_shape(self, herald, location): if location == PortLocation.INPUT: return self.herald_shape_in return self.herald_shape_out @dispatch(IDetector) def get_shape(self, detector): if detector.type == DetectionType.PNR: return self.pnr_detector_shape elif detector.type == DetectionType.Threshold: return self.threshold_detector_shape elif detector.type == DetectionType.PPNR: return self.ppnr_detector_shape raise TypeError(f"Unknown detector type: {detector.type}") def ffconf_shape(self, comp: AFFConfigurator, canvas, mode_style): w = self.get_width(comp) for i in range(comp.m): canvas.add_mpath(["M", 0, 25 + i * 50, "l", 50 * w, 0], **self.style[ModeType.CLASSICAL]) # Control wire between the feed-forward configurator and the configured circuit offset_sign = math.copysign(1, comp.circuit_offset) origin = [w * 25, 25 + offset_sign*15] destination = [w * 25, 40 + offset_sign*15 + 50 * comp.circuit_offset] if offset_sign > 0: # Move to the bottom of the ff configurator block if offset is "to the bottom" origin[1] += (comp.m - 1)*50 destination[1] += (comp.m - 1)*50 canvas.add_mline(origin + destination, stroke="white", stroke_width=PhysSkin._STROKE_WIDTH+2) canvas.add_mline(origin + destination, **self.style[ModeType.CLASSICAL], stroke_dasharray="4,4") origin[1] += offset_sign * 8 arrow_size = 3 for side in [-1, 1]: canvas.add_mline(origin + [origin[0] + side * arrow_size, origin[1] - offset_sign*arrow_size], **self.style[ModeType.CLASSICAL]) # The actual component canvas.add_rect((5, 10), 50 * w - 10, 50 * comp.m - 20, fill="lightgreen") canvas.add_text((w * 25, 30 + 50*(comp.m-1)/2), size=10, ta="middle", text=comp.name, max_size=50*w) def port_shape_in(self, port, canvas, mode_style): canvas.add_rect((-2, 15), 12, 50*port.m - 30, fill="lightgray") if port.name: canvas.add_text((-2, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="left", fontstyle="italic") def port_shape_out(self, port, canvas, mode_style): canvas.add_rect((15, 15), 12, 50*port.m - 30, fill="lightgray") if port.name: canvas.add_text((27, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="right", fontstyle="italic") def pnr_detector_shape(self, detector, canvas, mode_style): canvas.add_mpath(["M", 0, 25, "l", 25, 0], **self.style[ModeType.PHOTONIC]) if mode_style[0] is not None: canvas.add_mpath(["M", 25, 25, "l", 25, 0], **self.style[ModeType.CLASSICAL]) canvas.add_mpath(ShapeFactory.half_circle_port_out(10, 20), stroke="black", stroke_width=1, fill="lightgray") if detector.name: canvas.add_text((12, 12), text=detector.name, size=5, ta="left", fontstyle="italic") def threshold_detector_shape(self, detector, canvas, mode_style): canvas.add_mpath(["M", 0, 25, "l", 25, 0], **self.style[ModeType.PHOTONIC]) if mode_style[0] is not None: canvas.add_mpath(["M", 25, 25, "l", 25, 0], **self.style[ModeType.CLASSICAL]) canvas.add_mpath(ShapeFactory.triangle_port_out(10, 14), stroke="black", stroke_width=1, fill="lightgray") if detector.name: canvas.add_text((12, 12), text=detector.name, size=5, ta="left", fontstyle="italic") def ppnr_detector_shape(self, detector, canvas, mode_style): canvas.add_mpath(["M", 0, 25, "l", 25, 0], **self.style[ModeType.PHOTONIC]) if mode_style[0] is not None: canvas.add_mpath(["M", 25, 25, "l", 25, 0], **self.style[ModeType.CLASSICAL]) canvas.add_mpath(ShapeFactory.polygon_port_out(10, 12), stroke="black", stroke_width=1, fill="lightgray") if detector.name: canvas.add_text((12, 12), text=detector.name, size=5, ta="left", fontstyle="italic") def default_shape(self, circuit, canvas, mode_style): """ Default shape is a gray box """ w = self.get_width(circuit) content = self._get_display_content(circuit) for i in range(circuit.m): canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*w, 0], **self.style[mode_style[i]]) canvas.add_rect((5, 5), 50*w - 10, 50*circuit.m - 10, fill="gray") canvas.add_text((25*w, 25*circuit.m), size=7, ta="middle", text=content, max_size=50*w) @staticmethod def _reflective_side(theta, convention: cp.BSConvention) -> int: """ Return the reflective side of a beam splitter given a theta parameter and a BSConvention 1 means top -1 means bottom 0 means undecided """ if convention == cp.BSConvention.Rx: return 1 # top if not theta.defined: return 0 theta = float(theta) if convention == cp.BSConvention.Ry: return 1 if theta < 2*math.pi else -1 elif convention == cp.BSConvention.H: return -1 if round(theta/2/math.pi) % 2 else 1 def bs_shape(self, bs, canvas, mode_style): split_content = self._get_display_content(bs).split("\n") head_content = "\n".join([s for s in split_content if s.startswith("R=") or s.startswith("theta=")]) bottom_content_list = [s for s in split_content if not s.startswith("R=") and not s.startswith("theta=")] bottom_nline = len(bottom_content_list) bottom_size = 7 if bottom_nline < 3 else 6 mode_style = self.style[ModeType.PHOTONIC] canvas.add_mline([0, 25, 28, 25, 47, 44], stroke_linejoin="round", **mode_style) canvas.add_mline([53, 44, 72, 25, 100, 25], stroke_linejoin="round", **mode_style) canvas.add_mline([0, 75, 28, 75, 47, 56], stroke_linejoin="round", **mode_style) canvas.add_mline([53, 56, 72, 75, 100, 75], stroke_linejoin="round", **mode_style) canvas.add_rect((25, 43), 50, 14, fill="black") canvas.add_text((50, 80+5*bottom_nline), '\n'.join(bottom_content_list).replace('phi_', 'Φ_'), size=bottom_size, ta="middle") canvas.add_text((50, 26), head_content.replace('theta=', 'Θ='), size=7, ta="middle") # Choose the side of the gray rectangle in beam splitter representation r_side = self._reflective_side(bs.param('theta'), bs.convention) if r_side == 1: canvas.add_rect((25, 43), 50, 4, fill="lightgray") elif r_side == -1: canvas.add_rect((25, 53), 50, 4, fill="lightgray") # Add BS convention badge canvas.add_rect((68, 50), 10, 10, fill=bs_convention_color(bs.convention)) canvas.add_text((73, 57), bs.convention.name, size=6, ta="middle") def ps_shape(self, circuit, canvas, mode_style): canvas.add_mline([0, 25, 50, 25], **self.style[ModeType.PHOTONIC]) canvas.add_polygon([5, 40, 14, 40, 28, 10, 19, 10, 5, 40, 14, 40], stroke="black", fill="gray", stroke_width=1, stroke_linejoin="miter") content = self._get_display_content(circuit).replace("phi=", "Φ=") canvas.add_text((22, 38), text=content, size=7, ta="left") def lc_shape(self, circuit, canvas, mode_style): style = {'stroke': 'black', 'stroke_width': 1} canvas.add_mline([0, 25, 50, 25], **self.style[ModeType.PHOTONIC]) canvas.add_mline([25, 25, 25, 32], **style) canvas.add_mline([15, 32, 35, 32], **style) canvas.add_mline([18, 34, 32, 34], **style) canvas.add_mline([21, 36, 29, 36], **style) canvas.add_mline([24, 38, 26, 38], **style) canvas.add_rect((22, 22), 6, 6, fill="gray") canvas.add_text((6, 20), text=self._get_display_content(circuit), size=7, ta="left") def pbs_shape(self, circuit, canvas, mode_style): style = self.style[ModeType.PHOTONIC] canvas.add_mline([0, 25, 28, 25, 37.5, 37.5], **style, stroke_linejoin="round") canvas.add_mline([62.5, 37.5, 72, 25, 100, 25], **style, stroke_linejoin="round") canvas.add_mline([0, 75, 28, 75, 37.5, 62.5], **style, stroke_linejoin="round") canvas.add_mline([62.5, 62.5, 72, 75, 100, 75], **style, stroke_linejoin="round") canvas.add_mline([62.5, 62.5, 72, 75, 100, 75], **style, stroke_linejoin="round") canvas.add_polygon([25, 50, 50, 24, 75, 50, 50, 76, 25, 50], stroke="black", stroke_width=1, fill="gray") canvas.add_mline([25, 50, 75, 50], stroke="black", stroke_width=1) canvas.add_text((50, 86), text=self._get_display_content(circuit), size=7, ta="middle") def td_shape(self, circuit, canvas, mode_style): style = self.style[ModeType.PHOTONIC] for h_shift in [0, 9, 18]: canvas.add_circle((34 - h_shift, 14), 11, stroke_width=5, fill=None, stroke="white") canvas.add_circle((34 - h_shift, 14), 11, fill=None, **style) canvas.add_mline([0, 25, 19, 25], stroke="white", stroke_width=5) canvas.add_mline([0, 25, 19, 25], **style) canvas.add_mline([34, 25, 50, 25], stroke="white", stroke_width=5) canvas.add_mline([32, 25, 50, 25], **style) canvas.add_text((25, 38), self._get_display_content(circuit), 7, "middle") def unitary_shape(self, circuit, canvas, mode_style): m = circuit.m for i in range(m): canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*m, 0], **self.style[ModeType.PHOTONIC]) canvas.add_rect((5, 5), 50*m-10, 50*m-10, fill="lightyellow") canvas.add_text((25*m, 25*m), size=10, ta="middle", text=circuit.name, max_size=50*m) def barrier_shape(self, barrier, canvas, mode_style): if not barrier.visible: return m = barrier.m if canvas.background_color is None: canvas.add_rect((10, 10), 30, 50 * m - 20, fill="whitesmoke", stroke="whitesmoke") for i in range(m): style = self.style[mode_style[i]] if style["stroke"]: canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **style) canvas.add_rect((24, 10), 2, 50 * m - 20, fill="dimgrey", stroke="dimgrey") def perm_shape(self, circuit, canvas, mode_style): for an_input, an_output in enumerate(circuit.perm_vector): style = self.style[mode_style[an_input]] if style['stroke']: canvas.add_mline([3, 25+an_input*50, 47, 25+an_output*50], stroke="white", stroke_width=style["stroke_width"]+3) canvas.add_mline([0, 25+an_input*50, 3, 25+an_input*50, 47, 25+an_output*50, 50, 25+an_output*50], **style) def wp_shape(self, circuit, canvas, mode_style): params = self._get_display_content(circuit).replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") canvas.add_mline([0, 25, 50, 25], **self.style[ModeType.PHOTONIC]) canvas.add_rect((13, 7), width=14, height=36, fill="gray", stroke_width=1, stroke="black", stroke_linejoin="miter") canvas.add_mline([20, 7, 20, 43], stroke="black", stroke_width=1) canvas.add_text((28.5, 36), text=params[0], size=7, ta="left") canvas.add_text((28.5, 45), text=params[1], size=7, ta="left") def pr_shape(self, circuit, canvas, mode_style): canvas.add_mline([0, 25, 15, 25], **self.style[ModeType.PHOTONIC]) canvas.add_mline([35, 25, 50, 25], **self.style[ModeType.PHOTONIC]) canvas.add_rect((14, 14), width=22, height=22, stroke="black", fill="lightgray", stroke_width=1, stroke_linejoin="miter") canvas.add_mpath(ShapeFactory.pr_mpath, fill="black") canvas.add_text((25, 45), text=self._get_display_content(circuit).replace("delta=", "δ="), size=7, ta="middle") def subcircuit_shape(self, circuit, canvas, mode_style): w = self.style_subcircuit['width'] for idx in range(circuit.m): canvas.add_mline([0, 50*idx+25, w*50, 50*idx+25], **self.style[ModeType.PHOTONIC]) canvas.add_rect((2.5, 2.5), w*50 - 5, 50*circuit.m - 5, fill=self.style_subcircuit['fill'], **self.style_subcircuit['stroke_style']) title = circuit.name.upper() canvas.add_text((10, 16), title, 8, fontstyle="bold", max_size=w*50-15) def herald_shape_in(self, herald, canvas, mode_style): canvas.add_mpath(ShapeFactory.half_circle_port_in(10), stroke="black", stroke_width=1, fill="white") if herald.name: canvas.add_text((13, 41), text='[' + herald.name + ']', size=6, ta="middle", fontstyle="italic") canvas.add_text((17, 28), text=str(herald.expected), size=7, ta="middle") def herald_shape_out(self, herald, canvas, mode_style): if herald.detector_type is None or herald.detector_type == DetectionType.PNR: shape = ShapeFactory.half_circle_port_out(10) elif herald.detector_type == DetectionType.PPNR: shape = ShapeFactory.polygon_port_out(10) elif herald.detector_type == DetectionType.Threshold: shape = ShapeFactory.triangle_port_out(10) else: raise TypeError(f"Unknown detector type: {herald.detector_type}") canvas.add_mpath(shape, stroke="black", stroke_width=1, fill="white") if herald.name: canvas.add_text((13, 11), text='[' + herald.name + ']', size=6, ta="middle", fontstyle="italic") canvas.add_text((8, 28), text=str(herald.expected), size=7, ta="middle") def comp_circuit_shape(self, circuit, canvas, mode_style): m = circuit.m for i in range(m): canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*m, 0], **self.style[ModeType.PHOTONIC]) canvas.add_rect((5, 5), 50*m-10, 50*m-10, fill="orange") canvas.add_text((25*m, 25*m), size=10, ta="middle", text=circuit.name, max_size=50*m) ================================================ FILE: perceval/rendering/circuit/renderer_interface.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import ABC, abstractmethod from copy import deepcopy from perceval.utils import ModeType from perceval.components import ACircuit, Circuit, APort, CompiledCircuit class ICircuitRenderer(ABC): """ Base class for circuit renderers. Provides an interface to implement + a render_circuit() generic method. ICircuitRenderer internally works with circuit sizes in arbitrary units (AU), where single components and composite circuits size are measured by a given skin object. """ def __init__(self, nsize): self._nsize = nsize # number of modes self._mode_style = [ModeType.PHOTONIC] * nsize # A dictionary mapping a subblock to information pertaining to its # rendering. This is written by the pre-rendering pass, and read by # the main rendering pass. self._subblock_info = {} # Custom settings for the subblock being rendered. # They are loaded before open_subblock is invoked. self._current_subblock_info = {} def set_mode_style(self, index, style): self._mode_style[index] = style def render_circuit(self, circuit: ACircuit, shift: int = 0, recursive: bool = False, precision: float = 1e-6, nsimplify: bool = True) -> None: """Renders the input circuit""" if isinstance(circuit, CompiledCircuit) and circuit.template is not None: template = deepcopy(circuit.template) template.name = circuit.name for f, p in zip(circuit.parameters, template.get_parameters()): p.set_value(f) circuit = template if not isinstance(circuit, Circuit): self.append_circuit(tuple(p + shift for p in range(circuit.m)), circuit) if circuit.is_composite() and circuit.ncomponents() > 0: for r, c in circuit._components: shiftr = tuple(p + shift for p in r) if isinstance(c, CompiledCircuit) and c.template is not None and recursive: template = deepcopy(c.template) template.name = c.name for f, p in zip(c.parameters, template.get_parameters()): p.set_value(f) c = template if c.is_composite() and not isinstance(c, CompiledCircuit): if c._components: if recursive: self._current_subblock_info = self._subblock_info.setdefault(c, {}) self.open_subblock(shiftr, c.name, self.get_circuit_size(c, recursive=False), c._color) self.render_circuit( c, shift=shiftr[0], precision=precision, nsimplify=nsimplify) self.close_subblock(shiftr) else: self.append_subcircuit(shiftr, c) else: self.append_circuit(shiftr, c) self.extend_pos(shift, shift + circuit.m - 1) @abstractmethod def get_circuit_size(self, circuit: ACircuit, recursive: bool = False) -> tuple[int, int]: """ Returns the circuit size (in AU) """ @abstractmethod def max_pos(self, start, end) -> int: """ Returns the highest horizontal position on the circuit graph, between start and end modes (in AU) """ @abstractmethod def extend_pos(self, start: int, end: int, margin: int = 0) -> None: """ Extends horizontal position on the circuit graph, from modes 'start' to 'end' """ @abstractmethod def open(self) -> None: """ Starts the circuit drawing """ @abstractmethod def close(self) -> None: """ Finalizes circuit rendering when nothing more needs to be added. The opposite 'open' action should be run in __init__. """ @abstractmethod def open_subblock(self, lines: tuple[int, ...], name: str, size: tuple[int, int], color=None) -> None: """ Opens a visual area, highlighting a part of the circuit """ @abstractmethod def close_subblock(self, lines: tuple[int, ...]) -> None: """ Close a visual area """ @abstractmethod def draw(self) -> any: """ Finalize drawing, returns a fully drawn circuit (type is relative to the rendering method which was used). This should always be the last call. """ @abstractmethod def append_subcircuit(self, lines: tuple[int, ...], circuit: Circuit) -> None: """ Add a composite circuit to the rendering. Render each subcomponent independently. """ @abstractmethod def append_circuit(self, lines: tuple[int, ...], circuit: ACircuit) -> None: """ Add a component (or a circuit treated as a single component) to the rendering, on modes 'lines' """ @abstractmethod def add_mode_index(self, input_mode_style = None) -> None: """ Render mode indexes on the right and left side of a previously rendered circuit """ @abstractmethod def display_input_photons(self, input_pos, mode_style: list[ModeType]) -> None: """ Display photons on input modes """ @abstractmethod def add_out_port(self, m: int, port: APort) -> None: """ Render a port on the right side (outputs) of a previously rendered circuit, located on mode 'm' """ @abstractmethod def add_in_port(self, m: int, port: APort) -> None: """ Render a port on the left side (inputs) of a previously rendered circuit, located on mode 'm' """ @abstractmethod def add_detectors(self, detector_list: list) -> None: """ Render detectors when they exist """ def set_herald_info(self, info): """ Provides the renderer with a pre-computed dict mapping a component to information about which heralds are attached to its input and output ports. This is used to correctly position the Heralds within the circuit box, and not with the input and output ports. """ @property def subblock_info(self) -> dict: """ A dictionary mapping a subblock to a dictionary of settings relevant to its rendering. """ return self._subblock_info ================================================ FILE: perceval/rendering/circuit/skin_common.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.components.unitary_components import BSConvention BS_CONVENTION_COLOR = { BSConvention.Rx: 'thistle', BSConvention.Ry: 'lightsalmon', BSConvention.H: 'aquamarine' } def bs_convention_color(convention): if convention in BS_CONVENTION_COLOR: return BS_CONVENTION_COLOR[convention] return BS_CONVENTION_COLOR[BSConvention.Rx] ================================================ FILE: perceval/rendering/circuit/symb_skin.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math from multipledispatch import dispatch from perceval.components import AComponent, AFFConfigurator, Circuit, Port, PortLocation, Herald, IDetector,\ unitary_components as cp,\ non_unitary_components as nu,\ CompiledCircuit from ._canvas_shapes import ShapeFactory from .abstract_skin import ASkin, ModeType from .skin_common import bs_convention_color class SymbSkin(ASkin): def __init__(self, compact_display: bool = False): super().__init__({"stroke": "black", "stroke_width": 1}, {"width": 1, "fill": "white", "stroke_style": {"stroke": "black", "stroke_width": 1}}, compact_display) self.style[ModeType.CLASSICAL] = {"stroke": "gray", "stroke_width": 3} @dispatch(AComponent) def get_width(self, c) -> int: """Absolute fallback""" return 1 @dispatch(AFFConfigurator) def get_width(self, c) -> int: return self.get_width(c.circuit_template()) @dispatch((cp.Unitary, CompiledCircuit)) def get_width(self, c) -> int: return c.m @dispatch(Circuit) def get_width(self, c) -> int: return 2 @dispatch(cp.Barrier) def get_width(self, c: cp.Barrier) -> int: return 1 if c.visible else 0 @dispatch((cp.BS, cp.PBS)) def get_width(self, c) -> int: w = 1 if self._compact else 2 return w @dispatch((cp.PS, nu.TD, cp.PERM, cp.WP, cp.PR, nu.LC)) def get_width(self, c) -> int: return 1 @dispatch(AComponent) def get_shape(self, c): return self.default_shape @dispatch(AFFConfigurator) def get_shape(self, c): return self.ffconf_shape @dispatch(CompiledCircuit) def get_shape(self, c): return self.comp_circuit_shape @dispatch(cp.BS) def get_shape(self, c): return self.bs_shape @dispatch(cp.PS) def get_shape(self, c): return self.ps_shape @dispatch(cp.PBS) def get_shape(self, c): return self.pbs_shape @dispatch(nu.TD) def get_shape(self, c): return self.td_shape @dispatch(cp.Unitary) def get_shape(self, c): return self.unitary_shape @dispatch(cp.PERM) def get_shape(self, c): return self.perm_shape @dispatch(cp.WP) def get_shape(self, c): return self.wp_shape @dispatch(cp.HWP) def get_shape(self, c): return self.hwp_shape @dispatch(cp.QWP) def get_shape(self, c): return self.qwp_shape @dispatch(cp.PR) def get_shape(self, c): return self.pr_shape @dispatch(cp.Barrier) def get_shape(self, c): return self.barrier_shape @dispatch(nu.LC) def get_shape(self, c): return self.lc_shape @dispatch(Port, PortLocation) def get_shape(self, port, location): if location == PortLocation.INPUT: return self.port_shape_in return self.port_shape_out @dispatch(Herald, PortLocation) def get_shape(self, herald, location): if location == PortLocation.INPUT: return self.herald_shape_in return self.herald_shape_out def ffconf_shape(self, comp: AFFConfigurator, canvas, mode_style): w = self.get_width(comp) for i in range(comp.m): canvas.add_mpath(["M", 0, 25 + i * 50, "l", 50 * w, 0], **self.style[ModeType.CLASSICAL]) # Control wire between the feed-forward configurator and the configured circuit offset_sign = math.copysign(1, comp.circuit_offset) origin = [w * 25, 25 + offset_sign * 15] destination = [w * 25, 40 + offset_sign * 15 + 50 * comp.circuit_offset] if offset_sign > 0: # Move to the bottom of the ff configurator block if offset is "to the bottom" origin[1] += (comp.m - 1)*50 destination[1] += (comp.m - 1)*50 canvas.add_mline(origin + destination, stroke="white", stroke_width=4.5) canvas.add_mline(origin + destination, **self.style[ModeType.CLASSICAL], stroke_dasharray="9,5") origin[1] += offset_sign * 8 arrow_size = 5 for side in [-1, 1]: canvas.add_mline(origin + [origin[0] + side * arrow_size, origin[1] - offset_sign * arrow_size], **self.style[ModeType.CLASSICAL]) # The actual component canvas.add_rect((5, 10), 50 * w - 10, 50 * comp.m - 20, fill="honeydew") canvas.add_text((w * 25, 30 + 50*(comp.m-1)/2), size=10, ta="middle", text=comp.name, max_size=50*w) @dispatch(IDetector) def get_shape(self, detector): return self.detector_shape def default_shape(self, circuit, canvas, mode_style): """ Default shape is a gray box """ w = self.get_width(circuit) content = self._get_display_content(circuit) for i in range(circuit.m): canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*w, 0], **self.style[mode_style[i]]) canvas.add_rect((5, 5), 50*w - 10, 50*circuit.m - 10, fill="lightgray") canvas.add_text((25*w, 25*circuit.m), size=7, ta="middle", text=content, max_size=50*w) def bs_shape(self, bs, canvas, mode_style): canvas.add_mpath(ShapeFactory.bs_symbolic_mpath(self._compact), **self.style[ModeType.PHOTONIC]) content = self._get_display_content(bs).replace('phi', 'Φ').replace('theta=', 'Θ=') canvas.add_text((25 if self._compact else 50, 38), content, 7, "middle") # Add BS convention badge canvas.add_rect((35 if self._compact else 72, 53), 10, 10, fill=bs_convention_color(bs.convention)) canvas.add_text((40 if self._compact else 77, 60), bs.convention.name, size=6, ta="middle") def ps_shape(self, circuit, canvas, mode_style): canvas.add_mpath(["M", 0, 25, "h", 20, "m", 10, 0, "h", 20], **self.style[ModeType.PHOTONIC]) canvas.add_rect((15, 15), 20, 20, stroke="black", stroke_width=1, fill="lightgray") content = self._get_display_content(circuit).replace("phi=", "Φ=") canvas.add_text((25, 44), text=content, size=7, ta="middle") def lc_shape(self, circuit, canvas, mode_style): style = {'stroke': 'black', 'stroke_width': 1} canvas.add_mline([0, 25, 50, 25], **self.style[ModeType.PHOTONIC]) canvas.add_mline([25, 25, 25, 32], **style) canvas.add_mline([15, 32, 35, 32], **style) canvas.add_mline([18, 34, 32, 34], **style) canvas.add_mline([21, 36, 29, 36], **style) canvas.add_mline([24, 38, 26, 38], **style) canvas.add_rect((22, 22), 6, 6, fill="white") canvas.add_text((6, 20), text=self._get_display_content(circuit), size=7, ta="left") def pbs_shape(self, circuit, canvas, mode_style): if self._compact: path_data1 = ["M", 0, 25.1, "h", 11.049, "m", -11.049, 50, "h", 10.9375, "m", 27.9029, -50, "h", 11.1596, "m", -11.3283, 50, "h", 11.3283, "m", -11.3283, 0, "c", -10.0446, 0, -17.5781, -50, -27.7341, -50, "m", 27.9029, 0, "c", -10.7156, 0, -17.7467, 50, -27.7914, 50] path_data2 = ["M", 30, 50, "l", -4.7404, -5.2543, "l", -4.7404, 5.2543, "l", 4.7404, 5.2543, "l", 4.7404, -5.2543, "z", "m", 0.175, 0, "h", -9.6, "z"] else: path_data1 = ["M", 0, 25.1, "h", 22.0981, "m", -22.0981, 50, "h", 21.8751, "m", 55.8057, -50, "h", 22.3192, "m", -22.6566, 50, "h", 22.6566, "m", -22.6566, 0, "c", -20.0892, 0, -35.1561, -50, -55.4683, -50, "m", 55.8057, 0, "c", -21.4311, 0, -35.4935, 50, -55.5827, 50] path_data2 = ["M", 59, 50, "l", -9.4807, -10.5087, "l", -9.4807, 10.5087, "l", 9.4807, 10.5087, "l", 9.4807, -10.5087, "z", "m", 0.35, 0, "h", -19.2, "z"] canvas.add_mpath(path_data1, **self.style[ModeType.PHOTONIC]) canvas.add_mpath(path_data2, stroke_width=1, fill="#fff") canvas.add_text((25 if self._compact else 50, 86), text=self._get_display_content(circuit), size=7, ta="middle") def td_shape(self, circuit, canvas, mode_style): stroke = self.style[ModeType.PHOTONIC]['stroke'] for h_shift in [0, 9, 18]: canvas.add_circle((34 - h_shift, 14), 11, stroke="white", stroke_width=3) canvas.add_circle((34 - h_shift, 14), 11, stroke=stroke, stroke_width=2) canvas.add_mline([0, 25, 17, 25], stroke="white", stroke_width=3) canvas.add_mline([0, 25, 19, 25], stroke=stroke, stroke_width=2) canvas.add_mline([34, 25, 50, 25], stroke="white", stroke_width=3) canvas.add_mline([32, 25, 50, 25], stroke=stroke, stroke_width=2) canvas.add_text((25, 38), text=self._get_display_content(circuit), size=7, ta="middle") def unitary_shape(self, circuit, canvas, mode_style): w = circuit.m for i in range(w): canvas.add_mpath(["M", 0, 25 + i*50, "l", 50*w, 0], **self.style[ModeType.PHOTONIC]) shape = ShapeFactory.rounded_corner_square(6.25*w, 6) canvas.add_mpath(shape, **self.style[ModeType.PHOTONIC], fill="lightyellow") canvas.add_text((25*w, 25*w), size=10, ta="middle", text=circuit.name, max_size=50*w) def barrier_shape(self, barrier: cp.Barrier, canvas, mode_style): if not barrier.visible: return m = barrier.m canvas.add_mline((24, 10, 24, 50 * m - 16), stroke_width=1, stroke="lightgray") for i in range(m): style = self.style[mode_style[i]] if style["stroke"]: canvas.add_mpath(["M", 0, 25 + i*50, "l", 50, 0], **style) def perm_shape(self, circuit, canvas, mode_style): for an_input, an_output in enumerate(circuit.perm_vector): style = self.style[mode_style[an_input]] if style['stroke']: canvas.add_mpath(["M", 0, 24.8 + an_input * 50, "C", 20, 25 + an_input * 50, 30, 25 + an_output * 50, 50, 25 + an_output * 50], stroke="white", stroke_width=2) canvas.add_mpath(["M", 0, 25 + an_input * 50, "C", 20, 25 + an_input * 50, 30, 25 + an_output * 50, 50, 25 + an_output * 50], **style) def wp_shape(self, circuit, canvas, mode_style): params = self._get_display_content(circuit).replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") style = self.style[ModeType.PHOTONIC] canvas.add_mpath(["M", 0, 25, "h", 15, "m", 21, 0, "h", 15], **style) canvas.add_mpath(["M", 15, 45, "h", 21, "v", -40, "h", -21, "z"], **style) canvas.add_text((25, 55), text=params[0], size=7, ta="middle") canvas.add_text((25, 65), text=params[1], size=7, ta="middle") def hwp_shape(self, circuit, canvas, mode_style): params = self._get_display_content(circuit).replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") canvas.add_mpath(["M", 0, 25, "v", 0, "h", 0, "h", 50], **self.style[ModeType.PHOTONIC]) canvas.add_mpath(["M", 20, 0, "v", 50], stroke="black", stroke_width=2) canvas.add_mpath(["M", 30, 0, "v", 50], stroke="black", stroke_width=2) canvas.add_text((25, 60), text=params[0], size=7, ta="middle") def qwp_shape(self, circuit, canvas, mode_style): params = self._get_display_content(circuit).replace("xsi=", "ξ=").replace("delta=", "δ=").split("\n") canvas.add_mpath(["M", 0, 25, "v", 0, "h", 0, "h", 50], **self.style[ModeType.PHOTONIC]) canvas.add_mpath(["M", 25, 0, "v", 50], stroke="black", stroke_width=2) canvas.add_text((25, 60), text=params[0], size=7, ta="middle") def pr_shape(self, circuit, canvas, mode_style): canvas.add_mpath(["M", 0, 25, "h", 15, "m", 22, 0, "h", 15], **self.style[ModeType.PHOTONIC]) canvas.add_mpath(["M", 15, 36, "h", 22, "v", -22, "h", -22, "z"], stroke="black", stroke_width=1) canvas.add_mpath(ShapeFactory.pr_mpath, fill="black", stroke_width=0.1) canvas.add_text((27, 50), text=self._get_display_content(circuit).replace("delta=", "δ="), size=7, ta="middle") def subcircuit_shape(self, circuit, canvas, mode_style): w = self.style_subcircuit['width'] for idx in range(circuit.m): canvas.add_mline([0, 50*idx+25, w*50, 50*idx+25], **self.style[ModeType.PHOTONIC]) canvas.add_rect((2.5, 2.5), w*50 - 5, 50*circuit.m - 5, fill=self.style_subcircuit['fill'], **self.style_subcircuit['stroke_style']) title = circuit.name.upper() canvas.add_text((10, 8), title, 8, fontstyle="bold", max_size=w*50) def herald_shape_in(self, herald, canvas, mode_style): canvas.add_mpath(ShapeFactory.half_circle_port_in(10), stroke="black", stroke_width=1, fill="white") if herald.name: canvas.add_text((13, 41), text='[' + herald.name + ']', size=6, ta="middle", fontstyle="italic") canvas.add_text((17, 28), text=str(herald.expected), size=7, ta="middle") def herald_shape_out(self, herald, canvas, mode_style): canvas.add_mpath(ShapeFactory.half_circle_port_out(10), stroke="black", stroke_width=1, fill="white") if herald.name: canvas.add_text((13, 11), text='[' + herald.name + ']', size=6, ta="middle", fontstyle="italic") canvas.add_text((8, 28), text=str(herald.expected), size=7, ta="middle") def port_shape_in(self, port, canvas, mode_style): canvas.add_rect((-2, 15), 12, 50*port.m - 30, fill="white") if port.name: canvas.add_text((-2, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="left", fontstyle="italic") def port_shape_out(self, port, canvas, mode_style): canvas.add_rect((15, 15), 12, 50*port.m - 30, fill="white") if port.name: canvas.add_text((27, 50*port.m - 9), text='[' + port.name + ']', size=6, ta="right", fontstyle="italic") def detector_shape(self, detector, canvas, mode_style): canvas.add_mpath(["M", 0, 25, "l", 25, 0], **self.style[ModeType.PHOTONIC]) if mode_style[0] is not None: canvas.add_mpath(["M", 25, 25, "l", 25, 0], **self.style[ModeType.CLASSICAL]) canvas.add_mpath(ShapeFactory.half_circle_port_out(10, 20), stroke="black", stroke_width=1, fill="white") if detector.name: canvas.add_text((12, 12), text=detector.name, size=5, ta="left", fontstyle="italic") def comp_circuit_shape(self, circuit, canvas, mode_style): w = circuit.m for i in range(w): canvas.add_mpath(["M", 0, 25 + i * 50, "l", 50 * w, 0], **self.style[ModeType.PHOTONIC]) shape = ShapeFactory.rounded_corner_square(6.25 * w, 6) canvas.add_mpath(shape, **self.style[ModeType.PHOTONIC], fill="orange") canvas.add_text((25 * w, 25 * w), size=10, ta="middle", text=circuit.name, max_size=50 * w) ================================================ FILE: perceval/rendering/circuit/text_renderer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math from .renderer_interface import ICircuitRenderer from perceval.components import ACircuit, Barrier, PERM, APort, Herald from perceval.utils import format_parameters class TextRenderer(ICircuitRenderer): _PERM_DESC = '_╲ ╱\n_ ╳ \n_╱ ╲' # ASCII art representation of a permutation def __init__(self, nsize, hc=3, min_box_size=5): super().__init__(nsize) self._hc = hc self._h = [''] * (hc * nsize + 2) self._depth = [0] * nsize self._offset = 0 self.min_box_size = min_box_size self._ext_char = " " self.extend_pos(0, self._nsize - 1) def get_circuit_size(self, circuit: ACircuit, recursive: bool = False): return None # Don't need circuit size for text rendering def open(self): for k in range(self._nsize): self._h[self._hc * k + 2] += "──" def close(self): self.extend_pos(0, self._nsize - 1) for k in range(self._nsize): self._h[self._hc * k + 2] += "──" def max_pos(self, start, end, header=False): maxpos = 0 for nl in range(start * self._hc + (not header and 1 or 0), end * self._hc + 4 + (header and 1 or 0)): if len(self._h[nl]) > maxpos: maxpos = len(self._h[nl]) return maxpos def extend_pos(self, start, end, internal=False, header=False): char = self._ext_char maxpos = self.max_pos(start, end, header) for i in range(start * self._hc + (not header and 1 or 0), end * self._hc + 4 + ((header and not internal) and 1 or 0)): if internal: self._h[i] += char * (maxpos - len(self._h[i])) else: self._h[i] += ((i % self._hc) == 2 and "─" or char) * (maxpos - len(self._h[i])) def open_subblock(self, lines, name, size, color=None): start = lines[0] end = lines[-1] self.extend_pos(start, end, header=True) for k in range(start * self._hc, end * self._hc + 4): if k == start * self._hc: self._h[k] += "╔[" + name + "]" elif k % self._hc == 2: self._h[k] += "╫" else: self._h[k] += "║" self._h[end * self._hc + 4] += "╚" def close_subblock(self, lines): start = lines[0] end = lines[-1] self.extend_pos(start, end, header=True) for k in range(start * self._hc, end * self._hc + 4): if k == start * self._hc: self._h[k] += "╗" elif k % self._hc == 2: self._h[k] += "╫" else: self._h[k] += "║" self._h[end * self._hc + 4] += "╝" def append_subcircuit(self, lines, circuit): self.open_subblock(lines, circuit.name, None) self._ext_char = "░" self.extend_pos(lines[0], lines[-1], header=True, internal=True) self._ext_char = " " self.close_subblock(lines) def append_circuit(self, lines, circuit): # opening the box start = lines[0] end = lines[-1] self.extend_pos(start, end) if isinstance(circuit, Barrier): if circuit.visible: for k in range(start * self._hc + 1, (end + 1) * self._hc + 1): if k % self._hc == 2: self._h[k] += "──║──" else: self._h[k] += " ║ " self.extend_pos(start, end) return # put variables on the right number of lines if isinstance(circuit, PERM): content = self._PERM_DESC elif isinstance(circuit, ACircuit): content = format_parameters(circuit.get_variables(), 1e-3, False) else: content = "" content = circuit.name + (content and "\n" + content or "") lcontents = content.split("\n") if start == end: content = " ".join(lcontents) else: nperlines = math.ceil((len(lcontents) - 1) / ((end - start) * self._hc)) nlcontents = [lcontents[0]] idx = 1 pnlcontent = [] while idx < len(lcontents): pnlcontent.append(lcontents[idx]) idx += 1 if len(pnlcontent) == nperlines: nlcontents.append(" ".join(pnlcontent)) pnlcontent = [] if pnlcontent: nlcontents.append(" ".join(pnlcontent)) content = "\n".join(nlcontents) # display box opening for k in range(start, end + 1): self._depth[k] += 1 for k in range(start * self._hc + 1, end * self._hc + 3): if k == start * self._hc + 1: self._h[k] += "╭" elif k % self._hc == 2: self._h[k] += "┤" else: self._h[k] += "│" self._h[end * self._hc + 3] += "╰" lcontents = content.split("\n") maxw = max(len(nl) for nl in lcontents) maxw = max(maxw, self.min_box_size) # check if there are some "special effects" (centering _, right adjusting) for idx, l in enumerate(lcontents): if l.startswith("_"): lcontents[idx] = (" " * ((maxw - (len(l) - 1)) // 2)) + l[1:] for i in range(maxw): self._h[start * self._hc + 1] += "─" self._h[end * self._hc + 3] += "─" for j, l in enumerate(lcontents): if i < len(l): self._h[self._hc * start + 2 + j] += l[i] self.extend_pos(start, end, internal=True) # closing the box for k in range(start * self._hc + 1, end * self._hc + 3): if k == start * self._hc + 1: self._h[k] += "╮" elif k % self._hc == 2: self._h[k] += "├" else: self._h[k] += "│" self._h[end * self._hc + 3] += "╯" def draw(self): return "\n".join(self._h) def _set_offset(self, offset): offset_diff = offset - self._offset if offset_diff <= 0: return self._offset = offset for nl in range(len(self._h)): self._h[nl] = ' ' * offset_diff + self._h[nl] def add_mode_index(self, input_mode_style = None): offset = len(str(self._nsize)) + 1 self._set_offset(offset) for k in range(self._nsize): self._h[self._hc * k + 2] = f'{k:{offset-1}d}:' + self._h[self._hc * k + 2][offset:] self._h[self._hc * k + 2] += ':' + str(k) + f" (depth {self._depth[k]})" def display_input_photons(self, input_pos, mode_style) -> None: pass # Don't display input photons in text mode def add_out_port(self, n_mode: int, port: APort): content = '' if isinstance(port, Herald): content = port.expected for i in range(port.m): self._h[self._hc * (n_mode + i) + 2] += f'[{content})' self._h[self._hc * (n_mode + i) + 3] += f"[{port.name}]" def add_in_port(self, n_mode: int, port: APort): content = '' if isinstance(port, Herald): content = str(port.expected) shape_size = len(content) + 2 name = port.name name_size = len(name) self._set_offset(max(shape_size, name_size)) for i in range(port.m): self._h[self._hc * (n_mode + i) + 2] = f'({content}]' + \ '─' * (self._offset - shape_size) + \ self._h[self._hc * (n_mode + i) + 2][self._offset:] self._h[self._hc * (n_mode + i) + 3] = name + ' ' * \ (self._offset - name_size) + \ self._h[self._hc * (n_mode + i) + 3][self._offset:] def add_detectors(self, detector_list: list) -> None: pass # Don't display detectors in text mode ================================================ FILE: perceval/rendering/drawsvg_wrapper.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from typing import Any class DrawsvgWrapper: """This class is designed to contain single a drawsvg object It allows to check if a result will be a drawsvg object without knwowing its type""" def __init__(self, res: Any): self.value = res ================================================ FILE: perceval/rendering/format.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from enum import Enum class Format(Enum): """ Enum class used to specify the output format of pdisplay. The possible formats depend on the object type to render. """ TEXT = 1 MPLOT = 2 HTML = 3 LATEX = 4 Format.TEXT.__doc__ = "Text output, suitable for display in a terminal." Format.MPLOT.__doc__ = "Use the Matplotlib engine, opens its own graphic window or shows in an IDE graph view." Format.HTML.__doc__ = "Outputs HTML compatible code: SVG for images, for a table of data, etc." Format.LATEX.__doc__ = "Outputs LaTex code." ================================================ FILE: perceval/rendering/mplotlib_renderers/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: perceval/rendering/mplotlib_renderers/_mplot_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import matplotlib import os import platform import matplotlib.pyplot as plt from mpl_toolkits.mplot3d.axes3d import Axes3D import numpy import networkx as nx def autoselect_backend(): try: # The next line may raise an exception if the backend needs to be autodetected, because gtk* candidate backends # require cairo which is not available on Windows matplotlib.rcParams['backend'] except Exception: # We cannot guess the exception type we need to catch here: it can come from any Matplotlib # backend or third party. We do not have control over this code # In order to avoid matplotlib trying to use cairo (which is a dependency of cairocffi retrieved by drawsvg), # hint the backend given the execution context, and avoid cairo related backends at all cost! in_notebook = False in_pycharm_or_spyder = "PYCHARM_HOSTED" in os.environ or 'SPY_PYTHONPATH' in os.environ try: from IPython import get_ipython in_notebook = 'IPKernelApp' in get_ipython().config except (ImportError, AttributeError): pass try: if in_pycharm_or_spyder: matplotlib.use("module://backend_interagg") elif in_notebook: matplotlib.use("module://matplotlib_inline.backend_inline") elif platform.system() == "Darwin": matplotlib.use("MacOSX") else: import tkinter matplotlib.use("TkAgg") except Exception: # We want to catch anything that can happen above # Last chance: use "agg" non-interactive backend (which should work "anywhere"). matplotlib.use("agg") def _get_sub_figure(ax: Axes3D, array: numpy.array, basis_name: list): # Data size = array.shape[0] x = numpy.array([[i] * size for i in range(size)]).ravel() # x coordinates of each bar y = numpy.array([i for i in range(size)] * size) # y coordinates of each bar z = numpy.zeros(size * size) # z coordinates of each bar dxy = numpy.ones(size * size) * 0.5 # Width/Lenght of each bar dz = array.ravel() # length along z-axis of each bar (height) # Colors # get range of colorbars so we can normalize max_height = numpy.max(dz) min_height = numpy.min(dz) color_map = plt.get_cmap('viridis_r') if max_height != min_height: has_only_one_value = False # scale each z to [0,1], and get their rgb values rgba = [color_map((k - min_height) / max_height) for k in dz] else: has_only_one_value = True rgba = [color_map(0)] # Caption font_size = 6 # XY ax.set_xticks(numpy.arange(size) + 1) ax.set_yticks(numpy.arange(size) + 1) ax.tick_params(axis='x', which='major', labelsize=font_size) ax.set_xticklabels(basis_name) ax.tick_params(axis='y', which='major', labelsize=font_size) ax.set_yticklabels(basis_name) # Z if not has_only_one_value: ax.set_zlim(zmin=dz.min(), zmax=dz.max()) ax.tick_params('z', which='both', labelsize=font_size) ax.grid(True, axis='z', which='major', linewidth=2) # interval = [v for v in ax.get_zticks() if v > 0][0] # ax.zaxis.set_minor_locator(ticker.MultipleLocator(interval/5)) # Plot ax.bar3d(x, y, z, dxy, dxy, dz, color=rgba, alpha=0.7) ax.view_init(elev=30, azim=45) ================================================ FILE: perceval/rendering/mplotlib_renderers/density_matrix_renderer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import matplotlib.pyplot as plt from .._density_matrix_utils import _csr_to_rgb, _csr_to_greyscale, generate_ticks class DensityMatrixRenderer: def __init__(self, color: bool = True, cmap='hsv', mplot_noshow: bool = False, mplot_savefig: str = None): """ :meta private: :param output_format: :param color: whether to display the phase according to some circular cmap :param cmap: the cmap to use fpr the phase indication """ self.color = color self.cmap = cmap self.mplot_noshow = mplot_noshow self.mplot_savefig = mplot_savefig def render(self, dm): """ :meta private: :param dm: density matrix to be displayed """ fig = plt.figure() if self.color: img = _csr_to_rgb(dm.mat, self.cmap) plt.imshow(img) else: img = _csr_to_greyscale(dm.mat) plt.imshow(img, cmap='gray') l1, l2 = generate_ticks(dm) plt.yticks(l1, l2) plt.xticks([]) if not self.mplot_noshow: plt.show() if self.mplot_savefig: fig.savefig(self.mplot_savefig, bbox_inches="tight", format="svg") return "" ================================================ FILE: perceval/rendering/mplotlib_renderers/graph_renderer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import matplotlib.pyplot as plt import networkx as nx class GraphRenderer: def __init__(self): pass def render(self, g: nx.Graph): pos = nx.spring_layout(g, seed=42) nx.draw_networkx_nodes(g, pos, node_size=90, node_color='b') nx.draw_networkx_edges(g, pos) nx.draw_networkx_labels(g, pos, font_size=10, font_color='white', font_family="sans-serif") edge_labels = nx.get_edge_attributes(g, "weight") nx.draw_networkx_edge_labels(g, pos, edge_labels) plt.show() ================================================ FILE: perceval/rendering/mplotlib_renderers/tomography_renderer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import numpy from perceval.algorithm.tomography.abstract_process_tomography import AProcessTomography import matplotlib.pyplot as plt from ..mplotlib_renderers._mplot_utils import _get_sub_figure def _generate_pauli_captions(nqubit: int): from perceval.algorithm.tomography.tomography_utils import _generate_pauli_index pauli_indices = _generate_pauli_index(nqubit) pauli_names = [] for subset in pauli_indices: pauli_names.append([member.name for member in subset]) basis = [] for val in pauli_names: basis.append(''.join(val)) return basis class TomographyRenderer: def __init__(self, render_size, mplot_noshow: bool, mplot_savefig: str): self.render_size = render_size self.mplot_noshow = mplot_noshow self.mplot_savefig = mplot_savefig def render(self, qpt: AProcessTomography, precision: float): chi_op = qpt.chi_matrix() if self.render_size is not None and isinstance(self.render_size, tuple) and len(self.render_size) == 2: fig = plt.figure(figsize=self.render_size) else: fig = plt.figure() pauli_captions = _generate_pauli_captions(qpt._nqubit) significant_digit = int(math.log10(1 / precision)) # Real plot ax = fig.add_subplot(121, projection='3d') ax.set_title("Re[$\\chi$]") real_chi = numpy.round(chi_op.real, significant_digit) _get_sub_figure(ax, real_chi, pauli_captions) # Imag plot ax = fig.add_subplot(122, projection='3d') ax.set_title("Im[$\\chi$]") imag_chi = numpy.round(chi_op.imag, significant_digit) _get_sub_figure(ax, imag_chi, pauli_captions) if not self.mplot_noshow: plt.show() if self.mplot_savefig: fig.savefig(self.mplot_savefig, bbox_inches="tight", format="svg") return "" return None ================================================ FILE: perceval/rendering/pdisplay.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import copy import os from exqalibur import BSSamples from multipledispatch import dispatch import networkx as nx import sympy as sp from tabulate import tabulate from perceval.algorithm import Analyzer, AProcessTomography from perceval.components import (ACircuit, Circuit, AProcessor, Port, Herald, AFFConfigurator, Experiment, non_unitary_components as nl) from .drawsvg_wrapper import DrawsvgWrapper from .circuit.create_renderer import RendererFactory from .circuit import DisplayConfig, ASkin from perceval.utils import BasicState, Matrix, simple_float, simple_complex, DensityMatrix, mlstr, ModeType, Encoding from perceval.utils.logging import get_logger, channel from perceval.utils.states import StateVector, BSCount, BSDistribution, SVDistribution from perceval.runtime import JobGroup from .format import Format from ._processor_utils import collect_herald_info in_notebook = False def in_ide(): ide_detected = False for key in os.environ: if 'PYCHARM' in key or 'SPY_PYTHONPATH' in key or 'VSCODE' in key: ide_detected = True get_logger().debug(f"IDE detected: {ide_detected}", channel.general) return ide_detected try: from IPython import get_ipython if 'IPKernelApp' in get_ipython().config: in_notebook = True from IPython.display import display, Math, HTML except (ImportError, AttributeError): pass def pdisplay_circuit( circuit: ACircuit, output_format: Format = Format.TEXT, recursive: bool = False, compact: bool = False, precision: float = 1e-6, nsimplify: bool = True, skin: ASkin = None, **opts): if skin is None: skin = DisplayConfig.get_selected_skin(compact_display=compact) skin.precision = precision skin.nsimplify = nsimplify w, h = skin.get_size(circuit, recursive) renderer, _ = RendererFactory.get_circuit_renderer( circuit.m, output_format=output_format, skin=skin, total_width=w, total_height=h, **opts) renderer.open() renderer.render_circuit(circuit, recursive=recursive, precision=precision, nsimplify=nsimplify) renderer.close() renderer.add_mode_index() return renderer.draw() def pdisplay_processor(processor: AProcessor, output_format: Format = Format.TEXT, recursive: bool = False, compact: bool = False, precision: float = 1e-6, nsimplify: bool = True, skin: ASkin = None, **opts): return pdisplay_experiment(processor.experiment, output_format, recursive, compact, precision, nsimplify, skin, **opts) def pdisplay_experiment(processor: Experiment, output_format: Format = Format.TEXT, recursive: bool = False, compact: bool = False, precision: float = 1e-6, nsimplify: bool = True, skin: ASkin = None, **opts): n_modes = processor.circuit_size if skin is None: skin = DisplayConfig.get_selected_skin(compact_display=compact) skin.precision = precision skin.nsimplify = nsimplify w, h = skin.get_size(processor, recursive) renderer, pre_renderer = RendererFactory.get_circuit_renderer( n_modes, output_format=output_format, skin=skin, total_width=w, total_height=h, compact=compact, **opts) herald_info = {} if len(processor.in_heralds): for k in processor.in_heralds.keys(): renderer.set_mode_style(k, ModeType.HERALD) herald_info = collect_herald_info(processor, recursive) elif len(processor.heralds): herald_info = collect_herald_info(processor, recursive) original_mode_style = renderer._mode_style.copy() for rendering_pass in [pre_renderer, renderer]: if not rendering_pass: continue rendering_pass.set_herald_info(herald_info) rendering_pass.open() for r, c in processor.components: shift = r[0] if isinstance(c, ACircuit): c = Circuit(c.m).add(0, c) rendering_pass.render_circuit( c, recursive=recursive, precision=precision, nsimplify=nsimplify, shift=shift) if isinstance(c, AFFConfigurator): controlled_circuit = c.circuit_template() if isinstance(controlled_circuit, Circuit): controlled_circuit = Circuit(controlled_circuit.m).add(0, controlled_circuit) rendering_pass.render_circuit( controlled_circuit, recursive=recursive, shift=c.config_modes(r)[0] ) rendering_pass.close() if pre_renderer: # Pass pre-computed subblock info to the main rendering pass. renderer.subblock_info.update(pre_renderer.subblock_info) in_ports_drawn_on_modes = [] for port_range in processor._in_ports.values(): # Avoids adding ports on heralded modes and modes with ports already defined in_ports_drawn_on_modes += port_range if isinstance(processor._input_state, BasicState): renderer.display_input_photons(processor._input_state, original_mode_style) # In this case add mono-mode ports on all modes containing none empty_raw_port = Port(Encoding.RAW, "") for i in range(processor.circuit_size): if i not in in_ports_drawn_on_modes: renderer.add_in_port(i, empty_raw_port) for port, port_range in processor._in_ports.items(): renderer.add_in_port(port_range[0], port) renderer.add_detectors(processor._detectors) ports_drawn_on_modes = [] for port, port_range in processor._out_ports.items(): ports_drawn_on_modes += port_range if isinstance(port, Herald): det = processor._detectors[port_range[0]] if det is not None: port.detector_type = det.type renderer.add_out_port(port_range[0], port) for i in range(processor.circuit_size): if i not in ports_drawn_on_modes and \ i not in processor.detectors_injected and processor._detectors[i] is not None: renderer.add_out_port(i, Port(Encoding.RAW, "")) renderer.add_mode_index(original_mode_style) return renderer.draw() def pdisplay_matrix(matrix: Matrix, precision: float = 1e-6, output_format: Format = Format.TEXT) -> str: """ :meta private: Generates representation of a matrix """ def simp(value): if isinstance(value, (complex, float, int, sp.Number)) or (isinstance(value, sp.Expr) and len(value.free_symbols) == 0): return simple_complex(complex(value), precision=precision)[1] else: return value.__repr__() if output_format != Format.TEXT: marker = "$" if output_format == Format.HTML else "" if isinstance(matrix, sp.Matrix): return marker + sp.latex(matrix) + marker rows = [] for j in range(matrix.shape[0]): row = [] for v in matrix[j, :]: row.append(sp.S(simp(v))) rows.append(row) return marker + sp.latex(Matrix(rows, use_symbolic=True)) + marker if matrix.shape[0] == 1: return (mlstr("[") + mlstr(" ").join([simp(v) for v in matrix[0, :]]) + "]")._s else: s = mlstr("") for j in range(matrix.shape[1]): if j: s += " " s += "\n".join([simp(v) for v in matrix[:, j]]) h = s.height left_bracket = "⎡\n" + "⎢\n" * (h - 2) + "⎣" right_bracket = "⎤\n" + "⎥\n" * (h - 2) + "⎦" return (mlstr(left_bracket) + s + right_bracket)._s _TABULATE_FMT_MAPPING = { Format.TEXT: 'pretty', Format.MPLOT: 'pretty', Format.HTML: 'html', Format.LATEX: 'latex' } def pdisplay_analyzer(analyzer: Analyzer, output_format: Format = Format.TEXT, nsimplify: bool = True, precision: float = 1e-6): distribution = analyzer.distribution d = [] for iidx, _ in enumerate(analyzer.input_states_list): d.append([simple_float(f.real, nsimplify=nsimplify, precision=precision)[1] for f in list(distribution[iidx])]) return tabulate(d, headers=[analyzer._mapping.get(o, str(o)) for o in analyzer.output_states_list], showindex=[analyzer._mapping.get(i, str(i)) for i in analyzer.input_states_list], tablefmt=_TABULATE_FMT_MAPPING[output_format]) def pdisplay_state_distrib(sv: StateVector | BSDistribution | SVDistribution | BSCount, output_format: Format = Format.TEXT, nsimplify: bool | None = None, precision: float = 1e-6, max_v: int | None = None, sort: bool = True): """ :meta private: Displays StateVector and ProbabilityDistribution as a table of state vs probability (probability amplitude in StateVector's case) """ if nsimplify is None: # no numerical simplification by default if the number of displayed values is larger than 100 nsimplify = False if (min(max_v or len(sv), len(sv)) > 100) else True if sort: the_keys = sorted(sv.keys(), key=lambda a: -abs(sv[a])) else: the_keys = list(sv.keys()) d = [] for k in the_keys[:max_v]: value = sv[k] if isinstance(value, float): value = simple_float(value, nsimplify=nsimplify, precision=precision)[1] elif isinstance(value, complex): values = [] if value.real != 0: values.append(simple_float(value.real, nsimplify=nsimplify, precision=precision)[1]) if value.imag != 0: values.append("I*" + simple_float(value.imag, nsimplify=nsimplify, precision=precision)[1]) value = " + ".join(values) if values else "0" else: value = str(value) d.append([str(k), value]) headers = ["state", "probability"] if isinstance(sv, StateVector): headers[1] = "prob. ampl." elif isinstance(sv, BSCount): headers[1] = "count" s_states = tabulate(d, headers=headers, tablefmt=_TABULATE_FMT_MAPPING[output_format]) return s_states def pdisplay_bs_samples(bs_samples: BSSamples, output_format: Format = Format.TEXT, max_v: int | None = 10): s_states = tabulate([[str(sample)] for sample in bs_samples[:max_v]], headers=["states"], tablefmt=_TABULATE_FMT_MAPPING[output_format]) return s_states def pdisplay_tomography_chi(qpt: AProcessTomography, output_format: Format = Format.MPLOT, precision: float = 1E-6, render_size=None, mplot_noshow: bool = False, mplot_savefig: str = None): renderer = RendererFactory.get_tomography_renderer(output_format, render_size=render_size, mplot_noshow=mplot_noshow, mplot_savefig=mplot_savefig) return renderer.render(qpt, precision=precision) def pdisplay_density_matrix(dm, output_format: Format = Format.MPLOT, color: bool = True, cmap='hsv', mplot_noshow: bool = False, mplot_savefig: str = None): """ :meta private: :param dm: :param output_format: :param color: whether to display the phase according to some circular cmap :param cmap: the cmap to use fpr the phase indication """ renderer = RendererFactory.get_density_matrix_renderer(output_format, color=color, cmap=cmap, mplot_noshow=mplot_noshow, mplot_savefig=mplot_savefig) return renderer.render(dm) def pdisplay_graph(g: nx.Graph, output_format: Format = Format.MPLOT): renderer = RendererFactory.get_graph_renderer(output_format) return renderer.render(g) def pdisplay_job_group(jg: JobGroup, output_format: Format = Format.TEXT): progress = jg.progress() for key, value in progress.items(): if isinstance(value, int): progress[key] = [value] return tabulate(progress.values(), headers=['Job Category', 'Count', 'Details'], showindex=progress.keys(), tablefmt=_TABULATE_FMT_MAPPING[output_format]) @dispatch(object) def _pdisplay(o, **kwargs): raise NotImplementedError(f"pdisplay not implemented for {type(o)}") @dispatch(DensityMatrix) def _pdisplay(dm, **kwargs): return pdisplay_density_matrix(dm, **kwargs) @dispatch(AProcessTomography) def _pdisplay(qpt, **kwargs): return pdisplay_tomography_chi(qpt, **kwargs) @dispatch(JobGroup) def _pdisplay(jg, **kwargs): return pdisplay_job_group(jg, **kwargs) @dispatch((ACircuit, nl.TD, nl.LC)) def _pdisplay(circuit, **kwargs): return pdisplay_circuit(circuit, **kwargs) @dispatch(AProcessor) def _pdisplay(processor, **kwargs): return pdisplay_processor(processor, **kwargs) @dispatch(Experiment) def _pdisplay(experiment, **kwargs): return pdisplay_experiment(experiment, **kwargs) @dispatch(Matrix) def _pdisplay(matrix, **kwargs): return pdisplay_matrix(matrix, **kwargs) @dispatch(Analyzer) def _pdisplay(analyzer, **kwargs): return pdisplay_analyzer(analyzer, **kwargs) @dispatch((StateVector, BSDistribution, SVDistribution)) def _pdisplay(distrib, **kwargs): # Work on a copy, in order to not force normalization simply because of a display call normalized_dist = copy.copy(distrib) normalized_dist.normalize() return pdisplay_state_distrib(normalized_dist, **kwargs) @dispatch(BSCount) def _pdisplay(bsc, **kwargs): return pdisplay_state_distrib(bsc, **kwargs) @dispatch(BSSamples) def _pdisplay(bssamples, **kwargs): return pdisplay_bs_samples(bssamples, **kwargs) def _get_simple_number_kwargs(**kwargs): new_kwargs = {} keywords = ["precision", "nsimplify"] for kw in keywords: if kw in kwargs: new_kwargs[kw] = kwargs[kw] return new_kwargs @dispatch((int,float)) def _pdisplay(f, **kwargs): return simple_float(f, **_get_simple_number_kwargs(**kwargs))[1] @dispatch(complex) def _pdisplay(c, **kwargs): return simple_complex(c, **_get_simple_number_kwargs(**kwargs))[1] @dispatch(nx.Graph) def _pdisplay(g, **kwargs): return pdisplay_graph(g, **kwargs) def _default_output_format(o): """ Deduces the best output format given the nature of the data to be displayed and the execution context """ if in_notebook: if isinstance(o, Matrix): return Format.LATEX return Format.HTML elif in_ide() and (isinstance(o, (ACircuit, AProcessor, DensityMatrix, AProcessTomography, nl.TD, nl.LC, Experiment))): return Format.MPLOT return Format.TEXT def pdisplay(o, output_format: Format = None, **opts): """ Pretty display Main rendering entry point. Several data types can be displayed using pdisplay. :param o: Perceval object to render :param output_format: Format controls where and how a figure is render (in a interactive window, the terminal, etc.) - MPLOT: Matplotlib drawing (default in IDE - spyder, pycharm or vscode) - HTML: HTML for data table, SVG for circuits/processors (default in notebook) - TEXT: Pretty text display (default in another cases) - LATEX: LaTex code, drawing with Tikz for circuits/processors opts: - skin (rendering.circuit.PhysSkin, SymbSkin or DebugSkin or any ASkin subclass instance): Skin controls how a circuit/processor is displayed: - PhysSkin(): physical skin (default), - DebugSkin(): Similar to PhysSkin but modes are bigger, ancillary modes are displayed, components with variable parameters are red, - SymbSkin(): symbolic skin (thin black and white lines). - precision (float): numerical precision - nsimplify (bool): if True, tries to simplify numerical values by searching known values (pi, sqrt, fractions) - recursive (bool): if True, all hierarchy levels in a circuit/processor are displayed. Otherwise, only the top level is drawn, others are "black boxes" - max_v (int): Maximum number of displayed values in distributions - sort (bool): if True, sorts a distribution (descending order) before displaying - render_size: In SVG circuit/processor rendering, acts as a zoom factor (float) In Tomography display, is the size of the output plot in inches (tuple of two floats) """ if output_format is None: output_format = _default_output_format(o) get_logger().debug(f"Output format defaulted to {output_format.name}", channel.general) res = _pdisplay(o, output_format=output_format, **opts) if res is None: return if isinstance(res, DrawsvgWrapper): return res.value elif in_notebook and output_format == Format.LATEX: display(Math(res)) elif in_notebook and output_format == Format.HTML: display(HTML(res)) else: print(res) def pdisplay_to_file(o, path: str, output_format: Format = None, **opts): """ Directly saves the result of pdisplay into a file without actually displaying it. :param o: Perceval object to render :param path: Path to file to save :param output_format: See :code:`pdisplay` for details. Contrarily to :code:`pdisplay`, this method always uses Format.MPLOT by default so you might need to specify it by hand for some kinds of objects. :param opts: See :code:`pdisplay` for details. """ if output_format is None: output_format = Format.MPLOT if output_format == Format.MPLOT: opts['mplot_savefig'] = path opts['mplot_noshow'] = True res = _pdisplay(o, output_format=output_format, **opts) if res is None: raise RuntimeError(f"pdisplay_to_file not defined for type {type(o)}") if output_format == Format.MPLOT: return # File was generated by the _pdisplay call if output_format == Format.TEXT: with open(path, 'w', encoding='utf-8') as f_out: f_out.write(res) return if output_format == Format.HTML: _, output_ext = os.path.splitext(path) try: if output_ext == ".png": res.save_png(path) # May fail when rasterization is not available (i.e. on Windows) else: res.save_svg(path) return except Exception as e: get_logger().error(f"{e}", channel.general) if output_format == Format.LATEX: with open(path, 'w', encoding='utf-8') as f_out: f_out.write(res) return get_logger().warn( f"No output file could be created for {type(o)} object (format = {output_format.name}) at path {path}", channel.user) ================================================ FILE: perceval/runtime/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .job_status import JobStatus, RunningStatus from .job import Job from .local_job import LocalJob from .remote_job import RemoteJob from .remote_processor import RemoteProcessor from .session import ISession from .remote_config import RemoteConfig from .job_group import JobGroup from .check_cancel import cancel_requested from .payload_generator import PayloadGenerator ================================================ FILE: perceval/runtime/check_cancel.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. def cancel_requested(exec_request: dict = None): return exec_request is not None and exec_request.get('cancel_requested', False) ================================================ FILE: perceval/runtime/job.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing from abc import ABC, abstractmethod from perceval.utils.logging import get_logger, channel from .job_status import JobStatus class Job(ABC): def __init__(self, result_mapping_function: callable = None, command_param_names: list = None): self._results = None self._result_mapping_function = result_mapping_function self._param_names: list = command_param_names or [] self._name: str = "Job" @property def name(self) -> str: """ The job name """ return self._name @name.setter def name(self, new_name: str): if not isinstance(new_name, str): raise TypeError("A job name must be a string") self._name = new_name if len(new_name) > 0 else "unnamed" @property @abstractmethod def delta_parameters(self) -> dict: pass def _handle_params(self, *args, **kwargs): """Handle args and kwargs parameters from __call__, execute_sync or execute_async Split parameters between the command and the post-process function (mapping), See: self._delta_parameters and self._param_names """ args = list(args) if len(args) > len(self._param_names): self.delta_parameters['mapping']['max_samples'] = args.pop() for idx, unnamed_arg in enumerate(args): param_name = self._param_names[idx] if param_name in kwargs: # Parameter exists twice (in args and in kwargs) raise RuntimeError(f'Parameter named {param_name} was passed twice (in *args and **kwargs)') self.delta_parameters['command'][param_name] = unnamed_arg for k, v in self.delta_parameters['command'].items(): if v is None and k in kwargs: self.delta_parameters['command'][k] = kwargs[k] del kwargs[k] for k, v in self.delta_parameters['mapping'].items(): if v is None and k in kwargs: self.delta_parameters['mapping'][k] = kwargs[k] del kwargs[k] if kwargs: raise RuntimeError(f"Unused parameters in user call ({list(kwargs.keys())})") def __call__(self, *args, **kwargs) -> dict: """ Execute the job synchronously """ return self.execute_sync(*args, **kwargs) @property @abstractmethod def status(self) -> JobStatus: """ The job status metadata structure """ @property def is_complete(self) -> bool: return self.status.completed @property def is_failed(self) -> bool: return self.status.failed @property def is_success(self) -> bool: return self.status.success @property def is_waiting(self) -> bool: return self.status.waiting @property def is_running(self) -> bool: return self.status.running @abstractmethod def execute_sync(self, *args, **kwargs) -> dict: pass @abstractmethod def execute_async(self, *args, **kwargs) -> Job: """ Execute the task asynchronously. This call is non-blocking allowing for concurrency. Results cannot be expected to be ready as soon as this call ends. The results have to be retrieved only when the job status says it's completed. :param args: arguments to pass to the task function :param kwargs: keyword arguments to pass to the task function :return: self """ @abstractmethod def cancel(self): """ Request the cancellation of the job. """ @abstractmethod def _get_results(self): """Implemented get_results()""" def get_results(self) -> dict: """ Retrieve the results of the job. :return: results dictionary. You can expect a "results" or a "results_list" field, performance scores and other data corresponding to the job nature. :raises: RuntimeError if the job hasn't finished running, or if the results data are empty or malformed. """ job_status = self.status if not job_status.maybe_completed: raise RuntimeError('The job is still running, results are not available yet.') if job_status.canceled: get_logger().warn("Job has been canceled, trying to get partial result.", channel.user) if job_status.unknown: get_logger().warn("Unknown job status, trying to get result anyway.", channel.user) try: return self._get_results() except (KeyError, TypeError): if job_status.failed: raise RuntimeError(f'The job failed: {job_status.stop_message}') else: raise RuntimeError('Results are not available') ================================================ FILE: perceval/runtime/job_group.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import json import time from datetime import datetime from requests import HTTPError from tqdm import tqdm from perceval.runtime import Job, RemoteJob, RunningStatus from perceval.runtime.rpc_handler import RPCHandler from perceval.utils import PersistentData, FileFormat from perceval.utils.logging import get_logger, channel FILE_EXT_JGRP = 'jgrp' JGRP_DIR_NAME = "job_group" STATUS_REFRESH_DELAY = 5 DATE_TIME_FORMAT = "%Y%m%d_%H%M%S" class JobGroup: """ JobGroup handles a collection of remote jobs. A job group is named and persistent (job metadata will be written on disk). Job results will never be stored and will be retrieved every time from the Cloud. The JobGroup class can perform various tasks such as: - Saving information for a collection of jobs, whether they have been sent to the cloud or not. - Running jobs within the group either in parallel or sequentially. - Rerunning failed jobs within the group. - Retrieving all results at once. :param name: A name uniquely identifying the group (also, the filename used to save data on disk). If the same name is used more than once, jobs can be appended to the same group. """ _PERSISTENT_DATA = PersistentData() # Persistent data object for the job group class _DIR_PATH = _PERSISTENT_DATA.create_sub_directory(JGRP_DIR_NAME) def __init__(self, name: str): self._name = name now: datetime = datetime.now() self.created_date = now self.modified_date = now self._jobs: list[RemoteJob] = [] self._file_path = os.path.join(JobGroup._DIR_PATH, f"{self._name}.{FILE_EXT_JGRP}") if self._exists_on_disk(name): get_logger().info(f'Job Group with name {name} exists; subsequent jobs will be appended to it', channel.user) self._load_job_group() else: self._write_to_file() def __len__(self): return len(self._jobs) def __getitem__(self, index): return self._jobs[index] @property def name(self) -> str: """ Name of the job group """ return self._name @property def remote_jobs(self) -> list[RemoteJob]: """ Returns a chronologically ordered list of RemoteJobs in the group. """ return [job for job in self._jobs] def _to_json(self) -> dict: group_data = {'created_date': self.created_date.strftime(DATE_TIME_FORMAT), 'modified_date': self.modified_date.strftime(DATE_TIME_FORMAT), 'job_group_data': []} for job in self._jobs: dict_job = job._to_dict() group_data['job_group_data'].append(dict_job) return group_data def _from_json(self, json_data: dict) -> None: self.created_date = datetime.strptime(json_data['created_date'], DATE_TIME_FORMAT) self.modified_date = datetime.strptime(json_data['modified_date'], DATE_TIME_FORMAT) for job_entry in json_data['job_group_data']: self._jobs.append(self._build_remote_job(job_entry)) def _write_to_file(self) -> None: """ Writes job group data to disk """ self.modified_date = datetime.now() JobGroup._PERSISTENT_DATA.write_file(self._file_path, json.dumps(self._to_json()), FileFormat.TEXT) def _load_job_group(self) -> None: """ Creates a Job Group by loading an existing one from file """ group_data = json.loads(JobGroup._PERSISTENT_DATA.read_file(self._file_path, FileFormat.TEXT)) self._from_json(group_data) @staticmethod def _build_remote_job(job_entry: dict) -> RemoteJob: """ Returns a RemoteJob object recreated using its id and platform metadata """ metadata = job_entry['metadata'] user_token = metadata['headers']['Authorization'].split(' ')[1] rpc_handler = RPCHandler(metadata['platform'], metadata['url'], user_token, metadata.get('proxies')) return RemoteJob._from_dict(job_entry, rpc_handler) @staticmethod def list_locally_saved() -> list[str]: """ Returns a list of filenames of all JobGroups saved to disk """ jgrp_path = JobGroup._DIR_PATH files = [os.path.splitext(f)[0] for f in os.listdir(jgrp_path) if f.endswith(FILE_EXT_JGRP)] return files @staticmethod def _exists_on_disk(name: str) -> bool: """ Returns True if a JobGroup with an identical name is already saved on disk """ return JobGroup._PERSISTENT_DATA.has_file(os.path.join(JobGroup._DIR_PATH, name + '.' + FILE_EXT_JGRP)) def add(self, job_to_add: Job, **kwargs) -> None: """ Adds information of the new RemoteJob to an existing Group. Saves the data in a chronological order in the group (each entry is a dictionary of necessary information - status, id, body, metadata) :param job_to_add: a remote job to add to the list of existing job group :param kwargs: parameters to pass to the remote job, at execution """ if not isinstance(job_to_add, RemoteJob): raise TypeError(f'Only a RemoteJob can be added to a JobGroup (got {type(job_to_add)})') # Reject adding a duplicate RemoteJob if job_to_add.id and job_to_add.id in [job.id for job in self._jobs]: raise ValueError(f"Duplicate job detected : job id {job_to_add.id} exists in the group.") if kwargs: job_to_add._handle_params(**kwargs) self._jobs.append(job_to_add) self._write_to_file() def _update_job_statuses(self): """ Iterates over jobs in the group and updates their statuses on disk if a change is detected. """ for job in self._jobs: if job.was_sent and not job._job_status.completed: old_status = job._job_status.status current_status = job.status.status # /!\ May refresh the status with an HTTP request if old_status != current_status: self._write_to_file() def progress(self) -> dict: """ Iterates over all jobs in the group to create a dictionary of the current status of jobs. Jobs in the group are categorized as follows (depending on their RunningStatus on the Cloud) - Finished - successful {'SUCCESS'} - unsuccessful {'CANCELED', 'ERROR', 'UNKNOWN', 'SUSPENDED'} - Unfinished - sent {'WAITING', 'RUNNING', 'CANCEL_REQUESTED'} - not sent {None} :return: dictionary of the current status of jobs """ self._update_job_statuses() unsent_job_cnt = 0 success_job_cnt = 0 other_job_cnt = 0 sent_job_cnt = 0 for job in self._jobs: if not job.was_sent: unsent_job_cnt += 1 continue status = job._job_status if status.success: success_job_cnt += 1 continue if status.waiting or status.running: sent_job_cnt += 1 continue other_job_cnt += 1 fin_job_prog = {'successful': success_job_cnt, 'unsuccessful': other_job_cnt} unfin_job_prog = {'sent': sent_job_cnt, 'not sent': unsent_job_cnt} progress = dict() progress['Total'] = len(self._jobs) progress['Finished'] = [other_job_cnt + success_job_cnt, fin_job_prog] progress['Unfinished'] = [sent_job_cnt + unsent_job_cnt, unfin_job_prog] return progress def track_progress(self) -> None: """ Displays the status and progress of each job in the group using `tqdm` progress bars. Jobs are categorized into "Successful," "Running/Active on Cloud," and "Inactive/Unsuccessful." The method iterates over the list of jobs, continuously refreshing their statuses and updating the progress bars to provide real-time feedback until no "Running/Waiting" jobs remain on the Cloud. """ tot_jobs = len(self._jobs) # define tqdm bars bar_format = '{desc}{percentage:3.0f}%|{bar}|{n_fmt}/{total_fmt}' success_bar = tqdm(total=tot_jobs, bar_format=bar_format, desc="Successful Jobs", position=0, leave=True) active_bar = tqdm(total=len(self.list_active_jobs()), bar_format=bar_format, desc="Running/Waiting Jobs", position=1, leave=True) # non-active job can't become active, as long as this function is blocking inactive_bar = tqdm(total=tot_jobs, bar_format=bar_format, desc="Inactive/Unsuccessful Jobs", position=2, leave=True) while True: self._update_job_statuses() count_success = 0 count_running = 0 count_inactive = 0 for job in self._jobs: status = job._job_status if status.success: count_success += 1 continue if status.waiting or status.running: count_running += 1 continue count_inactive += 1 success_bar.n = count_success active_bar.n = count_running inactive_bar.n = count_inactive for bar in [success_bar, active_bar, inactive_bar]: bar.refresh() # needed to change the displayed value to bar.n if count_running == 0: break time.sleep(STATUS_REFRESH_DELAY) # delay before next acquisition of statuses success_bar.close() active_bar.close() inactive_bar.close() @staticmethod def delete_all_job_groups() -> None: """ Delete all existing groups on disk """ for each_file in JobGroup.list_locally_saved(): JobGroup.delete_job_group(each_file) @staticmethod def delete_job_group(name: str) -> None: """ Delete a single group by name :param name: name of the JobGroup to delete """ file_path = os.path.join(JobGroup._DIR_PATH, name + '.' + FILE_EXT_JGRP) JobGroup._PERSISTENT_DATA.delete_file(file_path) @staticmethod def delete_job_groups_date(del_before_date: datetime) -> None: """ Delete all saved groups created before a date. :param del_before_date: datetime of the oldest job group to keep. Anterior groups will be deleted. """ files_to_del = [] # list of files before date to delete for jg_name in JobGroup.list_locally_saved(): if JobGroup(jg_name).created_date < del_before_date: files_to_del.append(jg_name) if not files_to_del: get_logger().warn(f'No files found to delete before {del_before_date}', channel.user) # delete files for f in files_to_del: JobGroup.delete_job_group(f) def _list_jobs_status_type(self, statuses: list[RunningStatus]) -> list[RemoteJob]: remote_jobs = [] self._update_job_statuses() for job in self._jobs: if job.was_sent and job._job_status.status in statuses: remote_jobs.append(job) return remote_jobs def list_successful_jobs(self) -> list[RemoteJob]: """ Returns a list of all RemoteJobs in the group that have run successfully on the cloud. """ return self._list_jobs_status_type([RunningStatus.SUCCESS]) def list_active_jobs(self) -> list[RemoteJob]: """ Returns a list of all RemoteJobs in the group that are currently active on the cloud - those with a Running or Waiting status. """ return self._list_jobs_status_type([RunningStatus.RUNNING, RunningStatus.WAITING]) def list_unsuccessful_jobs(self) -> list[RemoteJob]: """ Returns a list of all RemoteJobs in the group that have run unsuccessfully on the cloud - errored or canceled """ return self._list_jobs_status_type([RunningStatus.ERROR, RunningStatus.CANCELED]) def list_unsent_jobs(self) -> list[RemoteJob]: """ Returns a list of all RemoteJobs in the group that have not been sent to the cloud """ return [job for job in self._jobs if not job.was_sent] def _launch_wait_jobs(self, concurrent_job_count: int | None, delay: float, rerun: bool, replace_failed_jobs: bool = False, sequential = False) -> None: """ Launches or reruns jobs in the group on Cloud in a parallel/sequential manner. :param concurrent_job_count: maximum number of concurrent jobs for each token. If not specified, the maximum number allowed by the cloud for this token is used. :param delay: number of seconds to wait between the launch of two consecutive jobs on cloud :param rerun: if True rerun failed jobs or run unsent jobs :param replace_failed_jobs: replace the rerun jobs in the jobgroup, else keep the failed in addition of the rerun ones :param sequential: if True, only one job is run at a time, including if several tokens have job availability """ jobs_to_run = self.list_unsuccessful_jobs() if rerun else self.list_unsent_jobs() awaited_jobs = set() count_success = 0 count_fail = 0 bar_format = '{percentage:3.0f}%|{bar}|{n_fmt}/{total_fmt}|{desc}' progress_bar = tqdm(total=len(jobs_to_run), bar_format=bar_format, desc="Successful: 0, Failed: 0") availability = self._get_jobs_availability(jobs_to_run, concurrent_job_count) while jobs_to_run or awaited_jobs: while any(availability[(job := j)._rpc_handler.token] for j in jobs_to_run): time.sleep(delay) job.set_job_group_name(self.name) token = job._rpc_handler.token availability[token] -= 1 jobs_to_run.remove(job) if job.status.failed: index = self._jobs.index(job) job = job.rerun() if replace_failed_jobs: self._jobs[index] = job else: self._jobs.append(job) else: job.execute_async() awaited_jobs.add(job) self._write_to_file() # save data after each job (rerun/execution) at launch if sequential: break time.sleep(STATUS_REFRESH_DELAY) availability = self._get_jobs_availability(jobs_to_run, concurrent_job_count) just_finished_jobs = set() for job in awaited_jobs: if job.status.completed: if job.status.success: count_success += 1 else: count_fail += 1 just_finished_jobs.add(job) self._write_to_file() # save data after a status update for a job progress_bar.update(1) progress_bar.set_description_str(f"Successful: {count_success}, Failed: {count_fail}") awaited_jobs.difference_update(just_finished_jobs) progress_bar.close() def run_sequential(self, delay: float) -> None: """ Launches the unsent jobs in the group on Cloud in a sequential manner with a user-specified delay between the completion of one job and the start of the next. :param delay: number of seconds to wait between launching jobs on cloud """ self._launch_wait_jobs(rerun=False, concurrent_job_count=1, delay=delay, sequential=True) def rerun_failed_sequential(self, delay: float, replace_failed_jobs=True) -> None: """ Reruns Failed jobs in the group on the Cloud in a sequential manner with a user-specified delay between the completion of one job and the start of the next. :param delay: number of seconds to wait between re-launching jobs on cloud :param replace_failed_jobs: Indicates whether a new job created from a rerun should replace the previously failed job (defaults to True). """ self._launch_wait_jobs(rerun=True, concurrent_job_count=1, delay=delay, replace_failed_jobs=replace_failed_jobs, sequential=True) def run_parallel(self) -> None: """ Launches all the unsent jobs in the group on Cloud, running them in parallel. The number of concurrent jobs is determined by the user pricing plan. """ self._launch_wait_jobs(concurrent_job_count=None, delay=0, rerun=False) def rerun_failed_parallel(self, replace_failed_jobs=True) -> None: """ Restart all failed jobs in the group on the Cloud, running them in parallel. The number of concurrent jobs is determined by the user pricing plan. :param replace_failed_jobs: Indicates whether a new job created from a rerun should replace the previously failed job (defaults to True). """ self._launch_wait_jobs(concurrent_job_count = None, delay=0, rerun=True, replace_failed_jobs=replace_failed_jobs) def launch_async_jobs(self, concurrent_job_count = None): """ Launches up to concurrent_job_count jobs and returns without waiting for job completion. :param concurrent_job_count: maximum number of concurrent jobs for each token. If not specified, the maximum number allowed by the cloud for each token is used. """ jobs_to_run = self.list_unsent_jobs() launched = 0 availability = self._get_jobs_availability(jobs_to_run, concurrent_job_count) for job in jobs_to_run: token = job._rpc_handler.token if availability[token] > 0: availability[token] -= 1 launched += 1 job.set_job_group_name(self.name) job.execute_async() self._write_to_file() # save data after each job (rerun/execution) at launch if not launched: get_logger().warn(f"{self.name}: no job will be run as there is no slot available") get_logger().info(f"{self.name}: {launched} jobs launched" f" / {len(self.list_unsent_jobs())} unsent jobs remaining") def relaunch_async_failed_jobs(self, replace_failed_jobs=True, concurrent_job_count = None): """ Relaunches up to concurrent_job_count failed jobs and returns without waiting for job completion. :param concurrent_job_count: maximum number of concurrent jobs for each token. If not specified, the maximum number allowed by the cloud for each token is used. :param replace_failed_jobs: replace the rerun jobs in the jobgroup, else keep the failed in addition of the rerun ones """ jobs_to_run = self.list_unsuccessful_jobs() launched = 0 availability = self._get_jobs_availability(jobs_to_run, concurrent_job_count) for job in jobs_to_run: token = job._rpc_handler.token if availability[token] > 0: # This makes a bug: if two tokens point to the same account, their availabilities are the same # The way to fix this is to call the availability at each job for which the registered availability is not 0 availability[token] -= 1 launched += 1 job.set_job_group_name(self.name) index = self._jobs.index(job) job = job.rerun() if replace_failed_jobs: self._jobs[index] = job else: self._jobs.append(job) self._write_to_file() # save data after each job (rerun/execution) at launch if not launched: get_logger().warn(f"{self.name}: no job will be run as there is no slot available") get_logger().info(f"{self.name}: {len(jobs_to_run)} jobs launched" f" / {len(self.list_unsent_jobs())} unsent jobs remaining") @staticmethod def _get_jobs_availability(jobs, concurrent_job_count = None): # TODO: This makes a bug: if two tokens point to the same account, their availabilities are the same # One way to fix this is to call the availability at each job for which the registered availability is not 0 availability = {} for job in jobs: rpc_handler = job._rpc_handler token = rpc_handler.token if token not in availability: try: response = rpc_handler.get_job_availability() nb_launchable = response["max_jobs_in_queue"] - response["num_jobs_in_queue"] except HTTPError: if not concurrent_job_count: raise ValueError("Unable to determine number of concurrent jobs;" " concurrent_job_count must be set manually") nb_launchable = concurrent_job_count if concurrent_job_count: nb_launchable = min(concurrent_job_count, nb_launchable) availability[token] = nb_launchable return availability def cancel_all(self): """ Cancels all started (already sent) jobs in the group. """ for job in self._jobs: if job.was_sent and not job._job_status.completed: try: job.cancel() except: pass self._write_to_file() def get_results(self) -> list[dict]: """ Retrieve results for all jobs in the group. It aggregates results by calling the `get_results()` method of each job object that have completed successfully. """ self._update_job_statuses() results = [] for job in self._jobs: if job._job_status.maybe_completed: try: results.append(job.get_results()) except RuntimeError: results.append(None) else: results.append(None) return results ================================================ FILE: perceval/runtime/job_status.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing from enum import Enum from time import time, sleep from perceval.utils.logging import get_logger, channel class RunningStatus(Enum): WAITING = 0 RUNNING = 1 SUCCESS = 2 ERROR = 3 CANCELED = 4 SUSPENDED = 5 CANCEL_REQUESTED = 6 UNKNOWN = 7 @staticmethod def from_server_response(res: str) -> RunningStatus: """Converts a job status name from the server to an enum value. .. note:: the server name for SUCCESS is "completed". :param res: the job status name :return: the corresponding enum value or UNKNOWN if the status name is unknown. """ if res == 'completed': return RunningStatus.SUCCESS else: try: return RunningStatus[res.upper()] except KeyError: get_logger().warn(f"Unknown job running status: {res}", channel.user) return RunningStatus.UNKNOWN @staticmethod def to_server_response(status: RunningStatus) -> str: """ Converts a job status enum value to an acceptable name for the server. .. note:: SUCCESS is converted to "completed". :param status: the job status enum value :return: the status name """ if status == RunningStatus.SUCCESS: return 'completed' else: return status.name.lower() RunningStatus.WAITING.__doc__ = ("The job is recorded on the Cloud but waits for a computing platform to be available " "in order to start.") RunningStatus.RUNNING.__doc__ = "The job is being run on a given computing platform." RunningStatus.SUCCESS.__doc__ = "The job has completed successfully. The full results are to be expected." RunningStatus.ERROR.__doc__ = "The job has failed and partial results might be available." RunningStatus.CANCELED.__doc__ = ("The job was canceled either by the user or the system. " "Partial results might be available.") RunningStatus.SUSPENDED.__doc__ = "The job was halted by the remote system and may be resumed later on." RunningStatus.CANCEL_REQUESTED.__doc__ = ("Transitional status leading to CANCELED. " "The Cloud sent a cancel order to the platform running the job.") RunningStatus.UNKNOWN.__doc__ = "An unknown status code was encountered." class JobStatus: """ Stores metadata related to a job execution """ def __init__(self): self._status: RunningStatus = RunningStatus.WAITING self._init_time_start = time() self._running_time_start: float | None = None self._duration: float | None = None self._completed_time: float | None = None self._running_progress: float = 0 self._running_phase: str | None = None self._stop_message: str | None = None self._waiting_progress: int | None = None self._last_progress_time: float = 0 def __call__(self) -> str: """ Return the name of the running status :return: name of the status """ return self._status.name @property def status(self) -> RunningStatus: """ :return: the job running status """ return self._status @status.setter def status(self, status: RunningStatus): self._status = status def start_run(self): """ Informs that the job is starting. Sets the job start time as the current time and the running status to "RUNNING" """ self._running_time_start = time() self._status = RunningStatus.RUNNING def stop_run(self, cause: RunningStatus = RunningStatus.SUCCESS, mesg: str | None = None): """ Informs that the job has just stopped. Sets the job stop time as the current time. :param cause: running status causing the end of the job :param mesg: optional additional message related to the end of the job """ self._status = cause self._completed_time = time() self._duration = self._completed_time - self._init_time_start if cause == RunningStatus.SUCCESS: self._running_progress = 1 self._stop_message = mesg def update_progress(self, progress: float, phase: str | None = None): """ Updates the job progress. :param progress: the current progress (between 0 and 1, 1 meaning 100%) :param phase: message related to the current progress """ if self._status == RunningStatus.WAITING: self.start_run() self._running_progress = progress self._running_phase = phase now = time() if self._last_progress_time and self._last_progress_time - now > 5: sleep(0.001) self._last_progress_time = now def update_times(self, creation_datetime: float, start_time: float, duration: float): """ Set the important times from external information :param creation_datetime: the timestamp the job was created :param start_time: the timestamp the job was started :param duration: the duration of the job (in seconds) """ self._init_time_start = creation_datetime self._running_time_start = start_time self._duration = duration if self.completed: self._completed_time = self._running_time_start + self._duration @property def creation_timestamp(self) -> float: """ :return: the timestamp the job was created """ return self._init_time_start @property def start_timestamp(self) -> float: """ :return: the timestamp the job was started """ return self._running_time_start @property def duration(self) -> float: """ :return: the duration of the job (in seconds) """ return self._duration @property def waiting(self) -> bool: """ :return: whether the job is in "WAITING" status """ return self._status == RunningStatus.WAITING @property def running(self) -> bool: """ :return: whether the job is running (corresponding statuses are "RUNNING" and "CANCEL_REQUESTED") """ return self._status in [RunningStatus.RUNNING, RunningStatus.CANCEL_REQUESTED] @property def completed(self) -> bool: """ :return: whether the job has completed, i.e. not waiting or running anymore (corresponding statuses are "SUCCESS", "ERROR" and "CANCELED") """ return self._status in [RunningStatus.SUCCESS, RunningStatus.ERROR, RunningStatus.CANCELED] @property def canceled(self) -> bool: """ :return: whether the job is in "CANCELED" status """ return self._status in [RunningStatus.CANCELED] @property def success(self) -> bool: """ :return: whether the job is in "SUCCESS" status """ return self._status in [RunningStatus.SUCCESS] @property def failed(self) -> bool: """ :return: whether the job has failed to complete (corresponding statuses are "CANCELED" and "ERROR") """ return self._status in [RunningStatus.CANCELED, RunningStatus.ERROR] @property def maybe_completed(self) -> bool: """ :return: whether the job has or might have completed (corresponding statuses are "SUCCESS", "ERROR", "CANCELED" and "UNKNOWN") """ return self._status in [RunningStatus.SUCCESS, RunningStatus.ERROR, RunningStatus.CANCELED, RunningStatus.UNKNOWN] @property def unknown(self) -> bool: """ :return: whether the job status is unknown """ return self._status in [RunningStatus.UNKNOWN] @property def stop_message(self) -> str | None: """ :return: the job stop message, if any. In case of a successful job, this will be `None`. """ return self._stop_message @property def progress(self) -> float: """ :return: the current job progress (between 0 and 1, 1 meaning 100%) """ return self._running_progress @property def running_time(self) -> float: if self._duration: return self._duration if not self.completed: return time() - self._running_time_start return self._completed_time - self._running_time_start def __str__(self) -> str: return self._status.name ================================================ FILE: perceval/runtime/local_job.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing import threading from perceval.utils.logging import get_logger, channel from .job import Job from .job_status import JobStatus, RunningStatus class LocalJob(Job): r""" Handle a computation task locally (i.e. on the computer running Perceval) :param fn: Function to be called by the job when run, aka "the task" :param result_mapping_function: Optional results post-processing function (e.g. can be used to convert results) :param delta_parameters: mapping of {'param_name': param_value} redirected to either the function or the mapping function :param command_param_names: names of `fn` parameters which can be mapped from the \*args of __call__ """ def __init__(self, fn: callable, result_mapping_function: callable = None, delta_parameters: dict = None, command_param_names: list = None): super().__init__(result_mapping_function=result_mapping_function, command_param_names=command_param_names) self._delta_parameters = delta_parameters or {"command": {}, "mapping": {}} self._fn = fn self._status = JobStatus() self._worker = None self._user_cb = None self._cancel_requested = False @property def delta_parameters(self) -> dict: return self._delta_parameters def set_progress_callback(self, callback: callable): # Signature must be (float, str | None) """ Set a progress callback function with the following signature: `def progress_callback(progress: float, message: str) -> dict | None` :param callback: callback function """ self._user_cb = callback @property def status(self) -> JobStatus: if self._status.running and not self._worker.is_alive(): self._status.stop_run() return self._status def _progress_cb(self, progress: float, phase: str | None = None): self._status.update_progress(progress, phase) if self._cancel_requested: return {'cancel_requested': True} if self._user_cb is not None: return self._user_cb(progress, phase) return None def execute_sync(self, *args, **kwargs) -> dict: """ Execute the task synchronously. This call is blocking and immediately returns a results dictionary. .. note:: This method has exactly the same effect as __call__. :param args: arguments to pass to the task function :param kwargs: keyword arguments to pass to the task function :return: the results """ assert self._status.waiting, "job has already been executed" if 'progress_callback' in kwargs: self._user_cb = kwargs['progress_callback'] self._delta_parameters['command']['progress_callback'] = self._progress_cb self._handle_params(*args, **kwargs) self._call_fn_safe(**self._delta_parameters['command']) return self.get_results() def _call_fn_safe(self, *args, **kwargs): """ Wrapping function called in the job thread in charge of catching exception and correctly setting status """ try: # it has already been called, calling it again to get more precise running time self._status.start_run() self._results = self._fn(*args, **kwargs) if self._cancel_requested: self._status.stop_run(RunningStatus.CANCELED, "User has canceled the job") else: self._status.stop_run() except Exception as e: get_logger().warn(f"An exception was raised during job execution.\n{type(e).__name__}: {e}", channel.user) self._status.stop_run(RunningStatus.ERROR, type(e).__name__+": "+str(e)) def execute_async(self, *args, **kwargs) -> LocalJob: assert self._status.waiting, "job has already been executed" if 'progress_callback' in kwargs: self._user_cb = kwargs['progress_callback'] self._delta_parameters['command']['progress_callback'] = self._progress_cb self._handle_params(*args, **kwargs) self._status.start_run() # we are launching the function in a separate thread self._worker = threading.Thread(target=self._call_fn_safe, kwargs=self._delta_parameters['command']) self._worker.start() return self def cancel(self): self._cancel_requested = True def _get_results(self): if self._results is None: return self._results if self._result_mapping_function: get_logger().info( f"Converting local job results with {self._result_mapping_function.__name__}", channel.general) if 'results' in self._results: self._results['results'] = self._result_mapping_function(self._results['results'], **self._delta_parameters['mapping']) elif 'results_list' in self._results: for res in self._results["results_list"]: mapping_args = {key: res["iteration"].get(key, val) for key, val in self._delta_parameters['mapping'].items()} res["results"] = self._result_mapping_function(res['results'], **mapping_args) else: raise KeyError("Cannot find either 'result' or 'results_list' in self._results") self._result_mapping_function = None return self._results ================================================ FILE: perceval/runtime/payload_generator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import uuid from perceval.components import Experiment from perceval.serialization import serialize from perceval.utils import PMetadata __process_id__ = uuid.uuid4() class PayloadGenerator: @staticmethod def generate_payload(command: str, experiment: Experiment = None, params: dict[str, any] = None, platform_name: str = "", **kwargs ) -> dict[str, any]: r""" Generate a simple payload containing the experiment, with the following template: { 'pcvl_version': ... 'process_id': ... 'platform_name': ... 'payload': { 'command': ... 'kwarg1': ... 'kwarg2': ... ... 'parameters': ... 'experiment': ... } } :param command: name of the method used :param experiment: (optional) Perceval experiment, by default an empty Experiment will be generated :param params: (optional) parameters to be listed in the 'parameters' section of the payload :param platform_name: (optional) name of the platform used Other parameters can be added to the payload via **kwargs. """ if experiment is None: experiment = Experiment() j = { 'pcvl_version': PMetadata.short_version(), 'process_id': str(__process_id__) } if platform_name: j['platform_name'] = platform_name payload = { 'command': command, **kwargs } if params: payload['parameters'] = params payload['experiment'] = serialize(experiment) j['payload'] = payload return j ================================================ FILE: perceval/runtime/remote_config.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os from perceval.utils import FileFormat from perceval.utils.logging import channel, get_logger, deprecated from perceval.utils.persistent_data import PersistentData, _CONFIG_FILE_NAME QUANDELA_CLOUD_URL = 'https://api.cloud.quandela.com' REMOTE_KEY = "remote" PROXIES_KEY = "proxies" URL_KEY = "url" TOKEN_KEY = "token" TOKEN_ENV_VAR = "PCVL_CLOUD_TOKEN" DEPRECATED_TOKEN_FILENAME = "token" class RemoteConfig: """Handle the remote configuration. :param persistent_data: The persistent data access to use. In a standard environment, always use the default. """ _token_env_var = TOKEN_ENV_VAR _proxies = None _token = None _url = None def __init__(self, persistent_data: PersistentData = PersistentData()): self._persistent_data = persistent_data def _get_deprecated_token(self): # check if a token is stored in the deprecated 'token' file token = None if self._persistent_data.has_file(DEPRECATED_TOKEN_FILENAME): get_logger().warn( f"The token should now be saved into '{_CONFIG_FILE_NAME}' instead of '{DEPRECATED_TOKEN_FILENAME}'." f"Use RemoteConfig class methods `set_token` then `save` to save the token into {_CONFIG_FILE_NAME}." f"The file '{DEPRECATED_TOKEN_FILENAME}' is deprecated.", channel.user) try: token = self._persistent_data.read_file(DEPRECATED_TOKEN_FILENAME, FileFormat.TEXT) except OSError: get_logger().warn("Cannot read token persistent file", channel.user) return token def _get_remote_config(self, key) -> str | dict[str, str] | None: config = self._persistent_data.load_config() if REMOTE_KEY in config: return config[REMOTE_KEY].get(key) return None @staticmethod def _get_token_from_env_var() -> str | None: return os.getenv(RemoteConfig._token_env_var) @staticmethod def set_proxies(proxies: dict[str, str]) -> None: """ Set the proxy configuration. Usage example: >>> rc = RemoteConfig() >>> rc.set_proxies({"http": "http://user:pass@192.168.0.1", ... "https": "http://user:pass@192.168.0.1:8080" ... }) :param proxies: proxy configuration in the form of a dictionary which maps protocols to URLs """ RemoteConfig._proxies = proxies def get_proxies(self) -> dict[str, str]: """Get the proxy configuration as a mapping of protocols to URLs.""" if not RemoteConfig._proxies: RemoteConfig._proxies = self._get_remote_config(PROXIES_KEY) return RemoteConfig._proxies or {} @staticmethod def set_url(url: str) -> None: """Set a cloud URL in the configuration cache. It is not saved on disk before the `save` method is called. :param url: The cloud URL """ RemoteConfig._url = url def get_url(self) -> str: """Search a valid cloud URL from the environment, put it in cache and return it. The priority for the URL search is as follows: * A URL already in cache (e.g. set by the user or already found in a previous call) * The value in Perceval persistent configuration :return: The cloud URL """ if not RemoteConfig._url: RemoteConfig._url = self._get_remote_config(URL_KEY) return RemoteConfig._url or QUANDELA_CLOUD_URL @staticmethod def set_token(token: str) -> None: """Set a user authentication token in the configuration cache. It is not saved on disk before the `save` method is called. :param token: The token """ RemoteConfig._token = token def get_token(self) -> str: f"""Search a valid token from the environment, put it in cache and return it. The priority for the token search is as follows: * A token already in cache (e.g. set by the user or already found in a previous call) * The value of the "{TOKEN_ENV_VAR}" environment variable * The value in Perceval persistent configuration :return: The token """ if not RemoteConfig._token: RemoteConfig._token = self._get_token_from_env_var() or self._get_remote_config(TOKEN_KEY) or self._get_deprecated_token() return RemoteConfig._token or "" @staticmethod def set_token_env_var(env_var: str) -> None: f"""Change the name of the environment variable storing a token. Default is {TOKEN_ENV_VAR}. :param env_var: name of the new environment variable to search for """ RemoteConfig._token_env_var = env_var # reload the token new_token = RemoteConfig._get_token_from_env_var() if new_token: RemoteConfig._token = new_token @staticmethod def get_token_env_var() -> str: """Get the name of the environment variable storing a token.""" return RemoteConfig._token_env_var @staticmethod @deprecated(version="v1.1", reason="RemoteConfig maximal job count is no longer used as it is now directly retrieved from the cloud") def set_cloud_maximal_job_count(count: int) -> None: pass @staticmethod @deprecated(version="v1.1", reason="RemoteConfig maximal job count is no longer used as it is now directly retrieved from the cloud") def get_cloud_maximal_job_count() -> int: return 0 @staticmethod def clear_cache(): """Delete the RemoteConfig cache.""" RemoteConfig._proxies = None RemoteConfig._url = None RemoteConfig._token = None def save(self) -> None: """Save the current remote configuration on disk. After this, the configuration is persistent and can be found in other Perceval sessions (even in different virtual envs).""" config = self._persistent_data.load_config() if REMOTE_KEY not in config: config[REMOTE_KEY] = {} config[REMOTE_KEY][PROXIES_KEY] = RemoteConfig._proxies config[REMOTE_KEY][URL_KEY] = RemoteConfig._url config[REMOTE_KEY][TOKEN_KEY] = RemoteConfig._token self._persistent_data.save_config(config) ================================================ FILE: perceval/runtime/remote_job.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing import json import time from requests.exceptions import HTTPError, ConnectionError from .job import Job from .job_status import JobStatus, RunningStatus from perceval.serialization import deserialize from perceval.serialization._serialized_containers import make_serialized, SerializedDict from perceval.utils.logging import get_logger, channel from .rpc_handler import RPCHandler def _retrieve_from_response(response: dict, field: str, default_value: any = '', value_type: type = str) -> any: if field not in response: get_logger().error(f"Missing field '{field}' from server response. Using default value {default_value}.", channel.general) return default_value try: result = value_type(response[field]) except (ValueError, TypeError): get_logger().error(f"The field '{field}' from server response contains the wrong value '{response[field]}'. Using default value {default_value}.", channel.general) result = default_value return result class RemoteJob(Job): r""" Handle a computation task remotely (i.e. through a Cloud provider) by sending a request body of the form: .. code-block:: { 'platform_name': '...', 'pcvl_version': 'M.m.p', 'payload': { 'command': '...', 'experiment': , ... other parameters } } :param request_data: prepared data for the job. It is extended by an execute_async() call before being sent. It is expected to be prepared by a RemoteProcessor. :param rpc_handler: a valid RPC handler to connect to a Cloud provider :param job_name: the job name (visible cloud-side) :param delta_parameters: parameters to add/remove dynamically :param job_context: Data on the job execution context (conversion required on results, etc.) :param command_param_names: List of parameter names for the command call (in order to resolve \*args in execute_async() call). This parameter is optional (default = empty list). However, without it, only \*\*kwargs are available in the execute_async() call :param refresh_progress_delay: wait time when running in sync mode between each refresh (in seconds) """ STATUS_REFRESH_DELAY = 1 # minimum job status refresh period (in s) _MAX_ERROR = 5 def __init__(self, request_data: dict, rpc_handler: RPCHandler, job_name: str, delta_parameters: dict = None, job_context: dict = None, command_param_names: list = None, refresh_progress_delay: int = 3): super().__init__(command_param_names=command_param_names) self._rpc_handler = make_serialized(rpc_handler) self._job_status = JobStatus() self._job_context = make_serialized(job_context) self._delta_parameters = make_serialized(delta_parameters or {"command": {}, "mapping": {}}) self._refresh_progress_delay = refresh_progress_delay # When syncing an async job (in s) self._previous_status_refresh = 0. self._status_refresh_error = 0 self._id = None self.name = job_name self._request_data = make_serialized(request_data) self._results = None @property def delta_parameters(self) -> SerializedDict: return self._delta_parameters @property def was_sent(self) -> bool: """ :return: True if the job was sent to a Cloud provider, False otherwise """ return self._id is not None # id is created when created on a Cloud @property def id(self) -> str | None: """ Job unique identifier :return: a UUID, or None if the job was never sent to a Cloud provider """ return self._id @staticmethod def from_id(job_id: str, rpc_handler: RPCHandler) -> RemoteJob: """ Recreate an existing RemoteJob from its unique identifier, and a RPCHandler. :param job_id: existing unique identifier :param rpc_handler: a RPCHandler with valid credentials to connect to the Cloud provider where the job was sent """ rj = RemoteJob(None, rpc_handler, "resumed") # There is no access to the job name, let's call it "resumed" rj._id = job_id rj.status() return rj @staticmethod def _from_dict(my_dict: dict, rpc_handler): # Perceval <= 1.0.1 stores 'body' containing the computed payload body = my_dict.get('body') # Perceval > 1.0.1 stores 'request_data' & 'delta_parameters' & 'job_context' allowing to re-computed the payload request_data = my_dict.get('request_data') job_context = my_dict.get('job_context') delta_parameters = my_dict.get('delta_parameters') if my_dict['status'] != 'SUCCESS' and not body and not request_data: raise RuntimeError(f"Missing job description") name = my_dict.get('name') rj = RemoteJob(request_data or body, rpc_handler, name, delta_parameters, job_context) rj._id = my_dict['id'] if my_dict['status'] is not None: rj._job_status.status = RunningStatus[my_dict['status']] return rj def _to_dict(self): job_info = dict() job_info['id'] = self.id job_info['name'] = self.name job_info['status'] = str(self._job_status) if self.was_sent else None # save metadata to recreate remote jobs job_info['metadata'] = {'headers': self._rpc_handler.headers, 'platform': self._rpc_handler.name, 'url': self._rpc_handler.url, 'proxies': self._rpc_handler.proxies} if not self._job_status.success: job_info['delta_parameters'] = self._delta_parameters job_info['job_context'] = self._job_context job_info['request_data'] = self._request_data return job_info def set_job_group_name(self, group_name: str): self._request_data['job_group_name'] = group_name def _handle_status_error(self, error): """ Handle a potentially non-blocking error After _MAX_ERROR errors in a row, the exception is raised """ self._status_refresh_error += 1 if self._status_refresh_error == self._MAX_ERROR: get_logger().error("Reached max number of HTTP errors in a row when updating job {self._id} status.", channel.general) raise error if isinstance(error, HTTPError): error_code = error.response.status_code if error_code in [ 408, # Time-out 409, # Conflict in the current state of the resource 421, # Misdirected request 423, # Resource locked 429 # Too many requests ]: get_logger().error(f"Got HTTP error {error_code} when updating job {self._id} status. Ignoring...", channel.general) else: # If the status code is any other error, it is considered unrecoverable raise error return self._job_status @property def status(self) -> JobStatus: if not self.was_sent or self._job_status.completed: # static status - retrieving from the cloud unnecessary. return self._job_status now = time.time() if now - self._previous_status_refresh > RemoteJob.STATUS_REFRESH_DELAY: self._previous_status_refresh = now try: response = self._rpc_handler.get_job_status(self._id) self._status_refresh_error = 0 except (HTTPError, ConnectionError) as error: self._handle_status_error(error) return self._job_status # Return previous status self._job_status.status = RunningStatus.from_server_response(_retrieve_from_response(response, 'status')) if self._job_status.running: self._job_status.update_progress(_retrieve_from_response(response, 'progress', 0., float), _retrieve_from_response(response, 'progress_message')) elif self._job_status.failed: self._job_status._stop_message = _retrieve_from_response(response, 'status_message') creation_datetime, duration, start_datetime = self._extract_job_times(response) self._job_status.update_times(creation_datetime, start_datetime, duration) return self._job_status def _extract_job_times(self, response: dict) -> tuple[float, float, int]: creation_datetime = _retrieve_from_response(response, 'creation_datetime', 0., float) start_datetime = 0. if not self._job_status.waiting: start_datetime = _retrieve_from_response(response, 'start_time', start_datetime, float) duration = 0 if self._job_status.completed: duration = _retrieve_from_response(response, 'duration', duration, int) return creation_datetime, duration, start_datetime def execute_sync(self, *args, **kwargs) -> dict: """ Execute the task synchronously. This call is blocking and immediately returns a results dictionary. .. note:: This method has exactly the same effect as __call__. .. warning:: A remote job natural way of running is asynchronous. This call will actually run the task asynchronously with a waiting loop to reproduce the behaviour of the synchronous call. :param args: arguments to pass to the task function :param kwargs: keyword arguments to pass to the task function :return: the results """ job = self.execute_async(*args, **kwargs) while not job.is_complete: time.sleep(self._refresh_progress_delay) return self.get_results() def _create_payload_data(self, *args, **kwargs) -> dict: # creates the payload for the job and returns the prepared job data self._handle_params(*args, **kwargs) if self._delta_parameters['mapping']: if self._job_context is None: self._job_context = {} self._job_context["mapping_delta_parameters"] = self._delta_parameters["mapping"] kwargs = self._delta_parameters['command'] kwargs['job_context'] = self._job_context request_data = self._request_data request_data['job_name'] = self._name request_data['payload'].update(kwargs) self._check_max_shots_samples_validity() return request_data def execute_async(self, *args, **kwargs) -> RemoteJob: assert self._job_status.waiting, "job has already been executed" try: self._id = self._rpc_handler.create_job(self._create_payload_data(*args, **kwargs)) get_logger().info(f"Send payload to the Cloud (got job id: {self._id})", channel.general) self._job_status.status = RunningStatus.WAITING except Exception as e: self._job_status.stop_run(RunningStatus.ERROR, str(e)) raise e return self def _check_max_shots_samples_validity(self): p = self._request_data['payload'] if "max_samples" in p and "max_shots" in p: if p["max_samples"] > p["max_shots"]: get_logger().warn(f"Lowered 'max_samples' from user defined value ({p['max_samples']}) to 'max_shots' value ({p['max_shots']}) for consistency.", channel.user) p["max_samples"] = p["max_shots"] def cancel(self): if self.status.status in (RunningStatus.RUNNING, RunningStatus.WAITING, RunningStatus.SUSPENDED): get_logger().info(f"Programmatically request job {self._id} cancellation", channel.general) try: self._rpc_handler.cancel_job(self._id) except HTTPError as e: raise HTTPError(f"Error while trying to cancel job: {e}") from None self._job_status.stop_run(RunningStatus.CANCEL_REQUESTED, 'Cancellation requested by user') else: raise RuntimeError('Job is not waiting or running, cannot cancel it') def rerun(self) -> RemoteJob: """Rerun a job. Same job will be executed again as a new one. Job must have failed, meaning job status is either CANCELED or ERROR. :raises RuntimeError: Job have not failed, therefore it cannot be rerun. :return: The new remote job. """ if not self.status.failed: raise RuntimeError( f"Cannot rerun current job because job status is: {self.status} (should be either CANCELED or ERROR)") job_dict = self._to_dict() try: job_dict['id'] = self._rpc_handler.rerun_job(self._id) except HTTPError as e: raise HTTPError(f"Error while trying to rerun job: {e}") from None job_dict['status'] = RunningStatus.WAITING.name return RemoteJob._from_dict(job_dict, self._rpc_handler) def _get_results(self) -> dict | None: if self._results and self.status.completed: return self._results try: response = self._rpc_handler.get_job_results(self._id) except HTTPError as e: raise HTTPError(f"Error while retrieving job results: {e}") from None self._results = deserialize(json.loads(response['results']), strict=False) if not isinstance(self._results, dict): self._results = None return self._results if "job_context" in self._results and 'result_mapping' in self._results["job_context"]: path_parts = self._results["job_context"]["result_mapping"] get_logger().info(f"Converting job {self._id} results with {path_parts[1]}", channel.general) module = __import__(path_parts[0], fromlist=path_parts[1]) result_mapping_function = getattr(module, path_parts[1]) # retrieve delta parameters from the response self._delta_parameters = self._results["job_context"].get( "mapping_delta_parameters", {}) if "results_list" in self._results: for res in self._results["results_list"]: mapping_args = {key: res["iteration"].get(key, val) for key, val in self._delta_parameters.items()} res["results"] = result_mapping_function(res['results'], **mapping_args) else: self._results["results"] = result_mapping_function( self._results["results"], **self._delta_parameters) return self._results def __str__(self): if not self.was_sent: return f"RemoteJob '{self.name}', id:{self._id}, status:not sent" else: return f"RemoteJob '{self.name}', id:{self._id}, status:{self._job_status}" ================================================ FILE: perceval/runtime/remote_processor.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from requests import HTTPError from perceval.components.abstract_processor import AProcessor, ProcessorType from perceval.components import ACircuit, Processor, AComponent, Experiment, Detector from perceval.utils import FockState, NoiseModel from perceval.utils.logging import get_logger, channel from perceval.serialization import deserialize from .remote_job import RemoteJob from .rpc_handler import RPCHandler from .remote_config import RemoteConfig from .payload_generator import PayloadGenerator PERFS_KEY = "perfs" TRANSMITTANCE_KEY = "Transmittance (%)" DEFAULT_TRANSMITTANCE = 0.06 class RemoteProcessor(AProcessor): @staticmethod def from_local_processor( processor: Processor, name: str = None, token: str = None, url: str = None, proxies: dict[str,str] = None, rpc_handler: RPCHandler = None): rp = RemoteProcessor( name=name, token=token, url=url, proxies=proxies, rpc_handler=rpc_handler) rp.noise = processor.noise rp.add(0, processor) rp.min_detected_photons_filter(processor._min_detected_photons_filter) if processor.input_state is not None: rp.with_input(processor.input_state) return rp def __init__(self, name: str = None, token: str = None, url: str = None, proxies: dict[str,str] = None, rpc_handler: RPCHandler = None, m: int = None, noise: NoiseModel = None): """ :param name: Platform name :param token: Token value to authenticate the user :param url: Base URL for the Cloud API to connect to :param proxies: Dictionary mapping protocol to the URL of the proxy :param rpc_handler: Inject an already constructed Remote Procedure Call handler (alternative init); when doing so, name, token and url are expected to be blank :param m: Initialize the processor to a given size (number of modes). If not set here, the first component or circuit added decides of the processor size :param noise: a NoiseModel containing noise parameters (defaults to no noise) simulated noise is ignored when working on a physical Quantum Processing Unit """ super().__init__(Experiment(m, noise=noise, name=name)) if rpc_handler is not None: # When a rpc_handler object is passed, name, token and url are expected to be None self._rpc_handler = rpc_handler self.name = rpc_handler.name # Here, we are mixing the experiment name and the Processor name if name is not None and name != self.name: get_logger().warn( f"Initialised a RemoteProcessor with two different platform names ({self.name} vs {name})", channel.user) self.proxies = rpc_handler.proxies else: remote = RemoteConfig() if name is None: raise ValueError("Parameter 'name' must have a value") if token is None: token = remote.get_token() if not token: raise ConnectionError("No token found") if url is None: url = remote.get_url() if proxies is None: proxies = remote.get_proxies() self.name = name self.proxies = proxies self._rpc_handler = RPCHandler(self.name, url, token, proxies) self._specs = {} self._perfs = {} self._status = None self._type = ProcessorType.SIMULATOR self._available_circuit_parameters = {} self.fetch_data() get_logger().info(f"Connected to Cloud platform {self.name}", channel.general) self._thresholded_output = "detector" in self._specs and self._specs["detector"] == "threshold" def _circuit_change_observer(self, new_component: Experiment | AComponent = None): pass # TODO: Check that the component matches what the platform can do # if new_component is not None: # if isinstance(new_component, Experiment): # if not new_component.is_unitary: # raise RuntimeError('Cannot compose a RemoteProcessor with a processor containing non linear components') # if new_component.has_feedforward: # raise RuntimeError('Cannot compose a RemoteProcessor with a processor containing feed-forward') # # elif not isinstance(new_component, IDetector) and not isinstance(new_component, ACircuit): # raise NotImplementedError("Non linear components not implemented for RemoteProcessors") def _noise_changed_observer(self): if self.noise and self._type == ProcessorType.PHYSICAL: # Injecting a noise model to an actual QPU makes no sense get_logger().warn( f"{self.name} is not a simulator but an actual QPU: user defined noise parameters will be ignored", channel.user) @property def is_remote(self) -> bool: return True def fetch_data(self): try: platform_details = self._rpc_handler.fetch_platform_details() except HTTPError as e: raise HTTPError(f"Error while fetching platform details: {e}") from None self._status = platform_details.get("status") platform_specs = deserialize(platform_details['specs'], strict=False) self._specs.update(platform_specs) if PERFS_KEY in platform_details: self._perfs.update(platform_details[PERFS_KEY]) if platform_details['type'] != 'simulator': self._type = ProcessorType.PHYSICAL @property def specs(self): return self._specs @property def performance(self): return self._perfs @property def constraints(self) -> dict: if 'constraints' in self._specs: return self._specs['constraints'] return {} @property def status(self): return self._status def check_circuit_size(self, m: int): if 'max_mode_count' in self.constraints and m > self.constraints['max_mode_count']: raise RuntimeError(f"Circuit too big ({m} modes > {self.constraints['max_mode_count']})") if 'min_mode_count' in self.constraints and m < self.constraints['min_mode_count']: raise RuntimeError(f"Circuit too small ({m} < {self.constraints['min_mode_count']})") if self.input_state is not None and self.input_state.m != m: raise RuntimeError(f"Circuit and input state size do not match ({m} != {self.input_state.m})") def check_circuit(self, circuit: ACircuit): self.check_circuit_size(circuit.m) def set_circuit(self, circuit: ACircuit): self.check_circuit(circuit) super().set_circuit(circuit) return self def get_rpc_handler(self): return self._rpc_handler @property def type(self) -> ProcessorType: return self._type def check_input(self, input_state: FockState) -> None: super().check_input(input_state) n_heralds = sum(self.heralds.values()) n_photons = input_state.n + n_heralds if 'max_photon_count' in self.constraints and n_photons > self.constraints['max_photon_count']: raise RuntimeError( f"Too many photons in input state ({input_state.n} + {n_heralds} heralds > {self.constraints['max_photon_count']})") if 'min_photon_count' in self.constraints and n_photons < self.constraints['min_photon_count']: raise RuntimeError( f"Not enough photons in input state ({n_photons} < {self.constraints['min_photon_count']})") if ('support_multi_photon' in self.constraints and not self.constraints['support_multi_photon'] and not all(mode_photon_cnt <= 1 for mode_photon_cnt in input_state)): raise RuntimeError(f"Input state ({input_state}) is not permitted. QPU/QPU simulators accept more than " f"1 photon per mode:{self.constraints['support_multi_photon']})") if self.m is not None and input_state.m != self.m: raise RuntimeError(f"Input state and circuit size do not match ({input_state.m} != {self.m})") @property def available_commands(self) -> list[str]: return self._specs.get("available_commands", []) def prepare_job_payload(self, command: str, **kwargs) -> dict[str, any]: self.check_min_detected_photons_filter() self.check_circuit_size(self.circuit_size) if self.input_state: self.check_input(self.remove_heralded_modes(self.input_state)) payload = PayloadGenerator.generate_payload(command, self.experiment, self._parameters, self.name, **kwargs) self.log_resources(command, self._parameters) return payload def resume_job(self, job_id: str) -> RemoteJob: return RemoteJob.from_id(job_id, self._rpc_handler) def _compute_sample_of_interest_probability(self, param_values: dict = None) -> float: if TRANSMITTANCE_KEY in self._perfs: transmittance = self._perfs[TRANSMITTANCE_KEY] / 100 else: transmittance = DEFAULT_TRANSMITTANCE get_logger().warn( f"No transmittance was found for {self.name}, using default {DEFAULT_TRANSMITTANCE}", channel.user) n = self.input_state.n photon_filter = n if self._min_detected_photons_filter is not None: photon_filter = self._min_detected_photons_filter + sum(self.heralds.values()) if photon_filter > n: return 0 if photon_filter < 2: return 1 # Simulation with a noisy source (only losses) exp = self.experiment.copy() if param_values is not None: params = exp.get_circuit_parameters() for param_name, value in param_values.items(): if param_name in params: params[param_name].set_value(value) lp = Processor("SLAP", exp, NoiseModel(transmittance=transmittance)) lp.min_detected_photons_filter(1) if self._thresholded_output: for m in range(lp.circuit_size): lp.add(m, Detector.threshold()) lp.with_input(self.input_state) probs = lp.probs() p_above_filter_ns = 0 for state, prob in probs['results'].items(): if state.n >= photon_filter: p_above_filter_ns += prob return p_above_filter_ns def estimate_required_shots(self, nsamples: int, param_values: dict = None) -> int: """ Compute an estimate number of required shots given the platform and the user request. The circuit, input state, minimum photon filter are taken into account. :param nsamples: Number of expected samples of interest :param param_values: Key/value pairs for variable parameters inside the circuit. All parameters need to be fixed for this computation to run. :return: Estimate of the number of shots the user needs to acquire enough samples of interest """ p_interest = self._compute_sample_of_interest_probability(param_values=param_values) if p_interest == 0: return None return round(nsamples / p_interest) def estimate_expected_samples(self, nshots: int, param_values: dict = None) -> int: """ Compute an estimate number of samples the user can expect given the platform and the user request. The circuit, input state, minimum photon filter are taken into account. :param nshots: Number of shots the user is willing to consume :param param_values: Key/value pairs for variable parameters inside the circuit. All parameters need to be fixed for this computation to run. :return: Estimate of the number of samples of interest the user can expect back """ p_interest = self._compute_sample_of_interest_probability(param_values=param_values) return round(nshots * p_interest) def log_resources(self, command: str, extra_parameters: dict): """Log resources of the remote processor :param command: name of the method used :param extra_parameters: extra parameters to log """ extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None} my_dict = { 'layer': 'RemoteProcessor', 'platform': self.name, 'm': self.circuit_size, 'command': command } if self.input_state: my_dict['n'] = self.input_state.n if self.noise: my_dict['noise'] = self.noise.__dict__() if extra_parameters: my_dict.update(extra_parameters) get_logger().log_resources(my_dict) def compute_physical_logical_perf(self, value: bool): """ Split performance into the physical and logical parts - when available :param value: True to compute the physical and logical performances, False otherwise. """ self.set_parameter("compute_physical_logical_perf", value) ================================================ FILE: perceval/runtime/rpc_handler.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """rpc handler module""" from __future__ import annotations import traceback from urllib.parse import quote_plus import requests from perceval.utils.logging import get_logger, channel _ENDPOINT_PLATFORM_DETAILS = '/api/platform/' _ENDPOINT_PLATFORM_DETAILS_NEW = '/api/platforms/' _PROCESSOR_SECTION = '/processor' _ENDPOINT_JOB_CREATE = '/api/job' _ENDPOINT_JOB_STATUS = '/api/job/status/' _ENDPOINT_JOB_CANCEL = '/api/job/cancel/' _ENDPOINT_JOB_RERUN = '/api/job/rerun/' _ENDPOINT_JOB_RESULT = '/api/job/result/' _ENDPOINT_JOB_AVAILABILITY = '/api/jobs/availability/' _JOB_ID_KEY = 'job_id' class RPCHandler: """Remote Call Procedure Handler A class to call the web API :param name: name of the target platform :param url: API URL to call :param token: token used for authentication :param proxies: dictionary mapping protocol to the URL of the proxy """ def __init__(self, name, url, token, proxies = None): self.name = name self.url = url self.proxies = proxies self.token = token or dict() self.headers = {'Authorization': f'Bearer {token}'} self.request_timeout = 10 # default timeout def build_endpoint(self, endpoint: str, *args: str): """build the default endpoint url :param endpoint: first part of the endpoint :return: the full endpoint url from the args """ endpath = '' if len(args) > 0: endpath = f"/{'/'.join(str(x).strip('/') for x in args)}" return f'{self.url}/{endpoint.strip("/")}{endpath}' def get_request(self, endpoint: str) -> None | dict: # requests may throw an IO Exception, let the user deal with it try: resp = requests.get(endpoint, headers=self.headers, timeout=self.request_timeout, proxies=self.proxies) resp.raise_for_status() except Exception as e: error_info = ''.join(traceback.format_stack()[:-1]) get_logger().debug(error_info, channel.general) raise e try: return resp.json() except Exception as e: error_info = ''.join(traceback.format_stack()[:-1]) get_logger().debug(error_info, channel.general) raise requests.HTTPError(f"Could not read json response from url: {endpoint}. \n{e}") def post_request(self, endpoint: str, payload: dict | None, with_json_response: bool) -> None | dict: # requests may throw an IO Exception, let the user deal with it try: request = requests.post(endpoint, headers=self.headers, json=payload, timeout=self.request_timeout, proxies=self.proxies) except Exception as e: error_info = ''.join(traceback.format_stack()[:-1]) get_logger().debug(error_info, channel.general) raise e json_res = None if with_json_response: try: json_res = request.json() except Exception as e: json_res = {'error': f'{e}'} if request.status_code != 200: error_info = ''.join(traceback.format_stack()) get_logger().debug(error_info, channel.general) raise requests.HTTPError(f"Url: {endpoint} answered with status code {request.status_code}.") return json_res def fetch_platform_details(self): """fetch platform details and settings""" quote_name = quote_plus(self.name) # first, try the new endpoint endpoint = self.build_endpoint(_ENDPOINT_PLATFORM_DETAILS_NEW, quote_name, _PROCESSOR_SECTION) try: response = self.get_request(endpoint) # TEMPORARY CODE # TODO: to be removed in version 1.2 if 'architecture' not in response.get('specs', {}): raise requests.HTTPError("Missing required 'architecture' field, falling back to the old endpoint " "containing the 'specific_circuit' field.") except requests.HTTPError: # try the old endpoint endpoint = self.build_endpoint(_ENDPOINT_PLATFORM_DETAILS, quote_name) response = self.get_request(endpoint) return response def create_job(self, payload) -> str: """create a job :param payload: the payload to send :raises HTTPError: when the API don't accept the payload :return: job id """ endpoint = self.build_endpoint(_ENDPOINT_JOB_CREATE) json_res = self.post_request(endpoint, payload, True) assert _JOB_ID_KEY in json_res, f'Missing {_JOB_ID_KEY} field in create_job response' return json_res[_JOB_ID_KEY] def cancel_job(self, job_id: str) -> None: """cancel a job :param job_id: id of the job to cancel """ endpoint = self.build_endpoint(_ENDPOINT_JOB_CANCEL, job_id) self.post_request(endpoint, None, False) def rerun_job(self, job_id: str) -> str: """Rerun a job as a another freshly created job :param job_id: job id to rerun :return: id of the new job """ endpoint = self.build_endpoint(_ENDPOINT_JOB_RERUN, job_id) json_res = self.post_request(endpoint, None, True) assert _JOB_ID_KEY in json_res, f'Missing {_JOB_ID_KEY} field in rerun_job response' return json_res[_JOB_ID_KEY] def get_job_status(self, job_id: str) -> dict: """get the status of a job :param job_id: if of the job :return: status of the job """ endpoint = self.build_endpoint(_ENDPOINT_JOB_STATUS, job_id) return self.get_request(endpoint) def get_job_results(self, job_id: str) -> dict: """get job results :param job_id: id of the job :return: results of the job """ endpoint = self.build_endpoint(_ENDPOINT_JOB_RESULT, job_id) return self.get_request(endpoint) def get_job_availability(self) -> dict: """get job availability :return: The availability of the token """ endpoint = self.build_endpoint(_ENDPOINT_JOB_AVAILABILITY) return self.get_request(endpoint) ================================================ FILE: perceval/runtime/session.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import ABC, abstractmethod from .remote_processor import RemoteProcessor class ISession(ABC): """ A session binds an authenticated user to a single remote platform on a given Cloud provider """ @abstractmethod def build_remote_processor(self) -> RemoteProcessor: """ Build a RemoteProcessor object given the session data """ def start(self): """Start session""" def stop(self): """Stop session""" def __enter__(self): self.start() return self def __exit__(self, exc_type, exc_val, exc_tb) -> bool: self.stop() return False # Do not suppress an exception that would be raised in the scope ================================================ FILE: perceval/serialization/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .serialize import serialize, serialize_to_file from ._state_serialization import deserialize_state, deserialize_state_list from .deserialize import deserialize, deserialize_circuit, circuit_from_file, deserialize_matrix, matrix_from_file, \ deserialize_float, deserialize_file from .serialize_binary import serialize_binary ================================================ FILE: perceval/serialization/_circuit_serialization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from multipledispatch import dispatch from perceval.components.compiled_circuit import CompiledCircuit from perceval.serialization import _schema_circuit_pb2 as pb from perceval.components import ACircuit, Circuit, AComponent, Herald, Port import perceval.components.unitary_components as comp import perceval.components.non_unitary_components as nu from perceval.components import FFConfigurator, FFCircuitProvider from perceval.serialization._matrix_serialization import serialize_matrix from perceval.serialization._parameter_serialization import serialize_parameter class ComponentSerializer: def __init__(self): self._pb = None def serialize(self, r: int, c: AComponent): self._pb = pb.Component() self._pb.starting_mode = r self._pb.n_mode = c.m self._serialize(c) return self._pb def _convert_bs_convention(self, convention): if convention == comp.BSConvention.H: return pb.BeamSplitter.H elif convention == comp.BSConvention.Ry: return pb.BeamSplitter.Ry return pb.BeamSplitter.Rx @dispatch(comp.BS) def _serialize(self, bs: comp.BS): pb_bs = pb.BeamSplitter() pb_bs.convention = self._convert_bs_convention(bs.convention) pb_bs.theta.CopyFrom(serialize_parameter(bs._theta)) pb_bs.phi_tl.CopyFrom(serialize_parameter(bs._phi_tl)) pb_bs.phi_bl.CopyFrom(serialize_parameter(bs._phi_bl)) pb_bs.phi_tr.CopyFrom(serialize_parameter(bs._phi_tr)) pb_bs.phi_br.CopyFrom(serialize_parameter(bs._phi_br)) self._pb.beam_splitter.CopyFrom(pb_bs) @dispatch(comp.PS) def _serialize(self, ps: comp.PS): pb_ps = pb.PhaseShifter() pb_ps.phi.CopyFrom(serialize_parameter(ps._phi)) if ps._max_error: pb_ps.max_error.CopyFrom(serialize_parameter(ps._max_error)) self._pb.phase_shifter.CopyFrom(pb_ps) @dispatch(comp.PERM) def _serialize(self, p: comp.PERM): pb_perm = pb.Permutation() pb_perm.permutations.extend(p.perm_vector) self._pb.permutation.CopyFrom(pb_perm) @dispatch(comp.Unitary) def _serialize(self, unitary: comp.Unitary): pb_umat = serialize_matrix(unitary.U) pb_unitary = pb.Unitary() pb_unitary.mat.CopyFrom(pb_umat) if unitary.name != comp.Unitary.DEFAULT_NAME: pb_unitary.name = unitary.name pb_unitary.use_polarization = unitary.requires_polarization self._pb.unitary.CopyFrom(pb_unitary) @dispatch(comp.PBS) def _serialize(self, _): pb_pbs = pb.PolarizedBeamSplitter() self._pb.polarized_beam_splitter.CopyFrom(pb_pbs) @dispatch(comp.QWP) def _serialize(self, wp: comp.QWP): pb_wp = pb.WavePlate() pb_wp.xsi.CopyFrom(serialize_parameter(wp._xsi)) self._pb.quarter_wave_plate.CopyFrom(pb_wp) @dispatch(comp.HWP) def _serialize(self, wp: comp.HWP): pb_wp = pb.WavePlate() pb_wp.xsi.CopyFrom(serialize_parameter(wp._xsi)) self._pb.half_wave_plate.CopyFrom(pb_wp) @dispatch(comp.WP) def _serialize(self, wp: comp.WP): pb_wp = pb.WavePlate() pb_wp.delta.CopyFrom(serialize_parameter(wp._delta)) pb_wp.xsi.CopyFrom(serialize_parameter(wp._xsi)) self._pb.wave_plate.CopyFrom(pb_wp) @dispatch(nu.TD) def _serialize(self, td: nu.TD): pb_td = pb.TimeDelay() pb_td.dt.CopyFrom(serialize_parameter(td._dt)) self._pb.time_delay.CopyFrom(pb_td) @dispatch(nu.LC) def _serialize(self, lc: nu.LC): pb_lc = pb.LossChannel() pb_lc.loss.CopyFrom(serialize_parameter(lc._loss)) self._pb.loss_channel.CopyFrom(pb_lc) @dispatch(comp.PR) def _serialize(self, pr: comp.PR): pb_pr = pb.PolarizationRotator() pb_pr.delta.CopyFrom(serialize_parameter(pr._delta)) self._pb.polarization_rotator.CopyFrom(pb_pr) @dispatch(comp.Barrier) def _serialize(self, barrier: comp.Barrier): pb_barrier = pb.Barrier() pb_barrier.visible = barrier.visible self._pb.barrier.CopyFrom(pb_barrier) @dispatch(FFConfigurator) def _serialize(self, ffconfigurator: FFConfigurator): pb_ffc = pb.FFConfigurator() pb_ffc.name = ffconfigurator.name pb_ffc.offset = ffconfigurator._offset pb_ffc.block_circuit_size = ffconfigurator._blocked_circuit_size pb_controlled = serialize_circuit(ffconfigurator._controlled) pb_ffc.controlled_circuit.CopyFrom(pb_controlled) pb_default_config = pb.VariableValues() for name, value in ffconfigurator._default_config.items(): pb_default_config.mapping[name] = value pb_ffc.default_config.CopyFrom(pb_default_config) for state, mapping in ffconfigurator._configs.items(): pb_vars = pb.VariableValues() for name, value in mapping.items(): pb_vars.mapping[name] = value pb_ffc.configs[str(state)].CopyFrom(pb_vars) self._pb.ff_configurator.CopyFrom(pb_ffc) @dispatch(FFCircuitProvider) def _serialize(self, ffcp: FFCircuitProvider): from ._experiment_serialization import serialize_experiment pb_ffcp = pb.FFCircuitProvider() pb_ffcp.name = ffcp.name pb_ffcp.offset = ffcp._offset pb_ffcp.block_circuit_size = ffcp._blocked_circuit_size dc = ffcp.default_circuit if isinstance(dc, ACircuit): pb_ffcp.circuit.CopyFrom(serialize_circuit(dc)) else: pb_ffcp.experiment.CopyFrom(serialize_experiment(dc)) for state, circ in ffcp._map.items(): pb_coe = pb.CircuitOrExperiment() if isinstance(circ, ACircuit): pb_coe.circuit.CopyFrom(serialize_circuit(circ)) else: pb_coe.experiment.CopyFrom(serialize_experiment(circ)) pb_ffcp.config_circ[str(state)].CopyFrom(pb_coe) self._pb.ff_circuit_provider.CopyFrom(pb_ffcp) @dispatch(Circuit) def _serialize(self, circuit: Circuit): pb_circ = serialize_circuit(circuit) self._pb.circuit.CopyFrom(pb_circ) @dispatch(CompiledCircuit) def _serialize(self, circuit: CompiledCircuit): pb_circ = serialize_compiled_circuit(circuit) self._pb.compiled_circuit.CopyFrom(pb_circ) def serialize_circuit(circuit: ACircuit) -> pb.Circuit: if not isinstance(circuit, Circuit): circuit = Circuit(circuit.m).add(0, circuit) pb_circuit = pb.Circuit() if circuit.name != Circuit.DEFAULT_NAME: pb_circuit.name = circuit.name pb_circuit.n_mode = circuit.m comp_serializer = ComponentSerializer() for r, c in circuit._components: pb_circuit.components.extend([comp_serializer.serialize(r[0], c)]) return pb_circuit def serialize_compiled_circuit(circuit: CompiledCircuit) -> pb.CompiledCircuit: pb_circuit = pb.CompiledCircuit() if circuit.name != Circuit.DEFAULT_NAME: pb_circuit.name = circuit.name pb_circuit.n_mode = circuit.m pb_circuit.parameters.extend(circuit.parameters) pb_circuit.version = str(circuit.version) return pb_circuit def serialize_component(component: AComponent) -> pb.Component: return ComponentSerializer().serialize(0, component) def serialize_herald(herald: Herald) -> pb.Herald: pb_herald = pb.Herald() pb_herald.autogenerated_name = herald._autogenerated_name if herald.user_given_name is not None: pb_herald.name = herald.user_given_name pb_herald.value = herald._value return pb_herald def serialize_port(port: Port) -> pb.Port: pb_port = pb.Port() pb_port.name = port.name pb_port.encoding = port.encoding.value return pb_port ================================================ FILE: perceval/serialization/_component_deserialization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from packaging.version import Version from perceval.serialization import _schema_circuit_pb2 as pb from perceval.serialization._parameter_serialization import deserialize_parameter from perceval.serialization._matrix_serialization import deserialize_pb_matrix import perceval.components.unitary_components as comp import perceval.components.non_unitary_components as nu from perceval.components.feed_forward_configurator import FFConfigurator, FFCircuitProvider from perceval.components.compiled_circuit import CompiledCircuit from perceval.utils import BasicState def deserialize_ps(serial_ps: pb.PhaseShifter, known_params: dict = None) -> comp.PS: max_error = deserialize_parameter(serial_ps.max_error, known_params) if max_error is not None: return comp.PS(deserialize_parameter(serial_ps.phi, known_params), max_error) return comp.PS(deserialize_parameter(serial_ps.phi, known_params)) def _convert_bs_convention(ser_convention): if ser_convention == pb.BeamSplitter.Ry: return comp.BSConvention.Ry elif ser_convention == pb.BeamSplitter.H: return comp.BSConvention.H return comp.BSConvention.Rx def deserialize_bs(serial_bs: pb.BeamSplitter, known_params: dict = None) -> comp.BS: conv = _convert_bs_convention(serial_bs.convention) return comp.BS(theta=deserialize_parameter(serial_bs.theta, known_params), phi_tl=deserialize_parameter(serial_bs.phi_tl, known_params), phi_bl=deserialize_parameter(serial_bs.phi_bl, known_params), phi_tr=deserialize_parameter(serial_bs.phi_tr, known_params), phi_br=deserialize_parameter(serial_bs.phi_br, known_params), convention=conv) def deserialize_perm(serial_perm, _) -> comp.PERM: return comp.PERM([x for x in serial_perm.permutations]) def deserialize_unitary(serial_unitary, _) -> comp.Unitary: m = deserialize_pb_matrix(serial_unitary.mat) return comp.Unitary(U=m) def deserialize_wp(serial_wp, known_params: dict = None) -> comp.WP: return comp.WP(deserialize_parameter(serial_wp.delta, known_params), deserialize_parameter(serial_wp.xsi, known_params)) def deserialize_qwp(serial_qwp, known_params: dict = None) -> comp.QWP: return comp.QWP(deserialize_parameter(serial_qwp.xsi, known_params)) def deserialize_hwp(serial_hwp, known_params: dict = None) -> comp.HWP: return comp.HWP(deserialize_parameter(serial_hwp.xsi, known_params)) def deserialize_dt(serial_dt, known_params: dict = None) -> nu.TD: return nu.TD(deserialize_parameter(serial_dt.dt, known_params)) def deserialize_lc(serial_lc, known_params: dict = None) -> nu.LC: return nu.LC(deserialize_parameter(serial_lc.loss, known_params)) def deserialize_pr(serial_pr, known_params: dict = None) -> comp.PR: return comp.PR(deserialize_parameter(serial_pr.delta, known_params)) def deserialize_pbs(_, __) -> comp.PBS: return comp.PBS() def deserialize_barrier(m: int, serial_barrier, _) -> comp.Barrier: return comp.Barrier(m, serial_barrier.visible) def deserialize_ff_configurator(m: int, serial_ffc, known_params: dict = None) -> FFConfigurator: from .deserialize import deserialize_circuit default_config = dict(serial_ffc.default_config.mapping) ffc = FFConfigurator(m, serial_ffc.offset, deserialize_circuit(serial_ffc.controlled_circuit, known_params), default_config, serial_ffc.name or None) for state_str, config in serial_ffc.configs.items(): config_dict = dict(config.mapping) ffc.add_configuration(BasicState(state_str), config_dict) if serial_ffc.block_circuit_size: ffc.block_circuit_size() return ffc def deserialize_ff_circuit_provider(m: int, serial_ffcp, known_params: dict = None) -> FFCircuitProvider: from .deserialize import deserialize_circuit, deserialize_experiment if serial_ffcp.WhichOneof('default_circuit') == "circuit": default_circ = deserialize_circuit(serial_ffcp.circuit, known_params) else: default_circ = deserialize_experiment(serial_ffcp.experiment, known_params) ffcp = FFCircuitProvider(m, serial_ffcp.offset, default_circ, serial_ffcp.name or None) for state_str, serial_circ in serial_ffcp.config_circ.items(): if serial_circ.WhichOneof('type') == "circuit": circ = deserialize_circuit(serial_circ.circuit, known_params) else: circ = deserialize_experiment(serial_circ.experiment, known_params) ffcp.add_configuration(BasicState(state_str), circ) if serial_ffcp.block_circuit_size: ffcp.block_circuit_size() return ffcp def deserialize_cc(serial_cc, known_params: dict = None) -> CompiledCircuit: name = serial_cc.name m = serial_cc.n_mode version = Version(serial_cc.version) parameters = serial_cc.parameters if not name: name = None compiled_circuit = CompiledCircuit(name, m, parameters, version) return compiled_circuit ================================================ FILE: perceval/serialization/_constants.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. SEP = ":" PCVL_PREFIX = f"{SEP}PCVL{SEP}" ZIP_PREFIX = f"{PCVL_PREFIX}zip{SEP}" MATRIX_TAG = "Matrix" CIRCUIT_TAG = "ACircuit" COMPONENT_TAG = "Component" EXPERIMENT_TAG = "Experiment" COMPILED_CIRCUIT_TAG = "CompiledCircuit" HERALD_TAG = "Herald" PORT_TAG = "Port" BS_TAG = "BasicState" SV_TAG = "StateVector" SVD_TAG = "SVDistribution" BSD_TAG = "BSDistribution" BSC_TAG = "BSCount" BSS_TAG = "BSSamples" NOISE_TAG = "NoiseModel" POSTSELECT_TAG = "PostSelect" BS_LAYERED_DETECTOR_TAG = "BSLayeredDetector" DETECTOR_TAG = "Detector" VALUE_NOT_SET = 0x0fffffff # Maximum writable value ================================================ FILE: perceval/serialization/_detector_serialization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.serialization import _schema_circuit_pb2 as pb from perceval.components import BSLayeredPPNR, Detector def serialize_bs_layer(detector: BSLayeredPPNR): pb_d = pb.BSLayeredPPNR() pb_d.name = detector.name pb_d.bs_layers = detector._layers pb_d.reflectivity = detector._r return pb_d def deserialize_bs_layer(pb_d: pb.BSLayeredPPNR) -> BSLayeredPPNR: detector = BSLayeredPPNR(pb_d.bs_layers, pb_d.reflectivity) detector.name = pb_d.name return detector def serialize_detector(detector: Detector): pb_d = pb.Detector() pb_d.name = detector.name if detector._wires is not None: pb_d.n_wires = detector._wires if detector.max_detections is not None: pb_d.max_detections = detector.max_detections if detector._wire_efficiency is not None: pb_d.wire_efficiency = detector._wire_efficiency return pb_d def deserialize_detector(pb_d: pb.Detector) -> Detector: n_wires = pb_d.n_wires or None max_detections = pb_d.max_detections or None wire_efficiency = pb_d.wire_efficiency or 1 # default value 0 is not allowed for Detectors detector = Detector(n_wires, max_detections, wire_efficiency) detector.name = pb_d.name return detector ================================================ FILE: perceval/serialization/_experiment_serialization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from multipledispatch import dispatch from perceval.components import Experiment, Herald, Port, APort, IDetector, BSLayeredPPNR, Detector, AComponent from perceval.serialization import _schema_circuit_pb2 as pb from perceval.serialization import serialize from perceval.serialization._circuit_serialization import serialize_port, serialize_herald, ComponentSerializer from perceval.serialization._constants import VALUE_NOT_SET from perceval.serialization._detector_serialization import serialize_detector, serialize_bs_layer class ExperimentSerializer: def __init__(self): self._serialized = None def serialize(self, experiment: Experiment): self._serialized = pb.Experiment() if experiment.input_state is not None: self._serialized.input_state = serialize.serialize(experiment.input_state) self._serialized.name = experiment.name if experiment.noise is not None: self._serialized.noise_model = serialize.serialize(experiment.noise) if experiment.post_select_fn is not None: self._serialized.post_select = serialize.serialize(experiment.post_select_fn) self._serialized.n_mode = experiment.circuit_size if experiment.min_photons_filter: self._serialized.min_photons_filter = experiment.min_photons_filter else: self._serialized.min_photons_filter = VALUE_NOT_SET self._serialize_ports(experiment._in_ports, experiment._out_ports) self._serialize_detectors(experiment.detectors) self._serialize_components(experiment.components) return self._serialized @staticmethod @dispatch(Herald) def _serialize_port(port): pb_port = pb.APort() pb_port.herald.CopyFrom(serialize_herald(port)) return pb_port @staticmethod @dispatch(Port) def _serialize_port(port): pb_port = pb.APort() pb_port.port.CopyFrom(serialize_port(port)) return pb_port def _serialize_ports(self, in_ports: dict[APort, list[int]], out_ports: dict[APort, list[int]]): for port, modes in in_ports.items(): pb_port = self._serialize_port(port) self._serialized.input_ports[modes[0]].CopyFrom(pb_port) for port, modes in out_ports.items(): pb_port = self._serialize_port(port) self._serialized.output_ports[modes[0]].CopyFrom(pb_port) @staticmethod @dispatch(BSLayeredPPNR) def _serialize_detector(detector): pb_detector = pb.IDetector() pb_detector.ppnr.CopyFrom(serialize_bs_layer(detector)) return pb_detector @staticmethod @dispatch(Detector) def _serialize_detector(detector): pb_detector = pb.IDetector() pb_detector.detector.CopyFrom(serialize_detector(detector)) return pb_detector def _serialize_detectors(self, detectors: list[IDetector]): for i, detector in enumerate(detectors): if detector is not None: pb_detector = self._serialize_detector(detector) self._serialized.detectors[i].CopyFrom(pb_detector) def _serialize_components(self, components: list[tuple[tuple, AComponent]]): comp_serializer = ComponentSerializer() for r, c in components: if not isinstance(c, IDetector): self._serialized.components.extend([comp_serializer.serialize(r[0], c)]) def serialize_experiment(experiment: Experiment): return ExperimentSerializer().serialize(experiment) ================================================ FILE: perceval/serialization/_matrix_serialization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.serialization import _schema_circuit_pb2 as pb import numpy as np from perceval.utils import Matrix from perceval.serialization._parameter_serialization import deserialize_parameter def serialize_matrix(m: Matrix) -> pb.Matrix: pb_mat = pb.Matrix() pb_mat.rows = m.shape[0] pb_mat.cols = m.shape[1] values = [] if m.is_symbolic(): pb_symbolic = pb.MatrixSymbolic() for x in m.vec(): pb_param = pb.Parameter() pb_param.expression = str(x) values.append(pb_param) pb_symbolic.data.extend(values) pb_mat.symbolic.CopyFrom(pb_symbolic) else: pb_numeric = pb.MatrixDouble() values = [] for x in np.nditer(m): pb_complex = pb.ComplexDouble() pb_complex.real_value = float(x.real) pb_complex.imaginary_value = float(x.imag) values.append(pb_complex) pb_numeric.data.extend(values) pb_mat.numeric.CopyFrom(pb_numeric) return pb_mat def _deserialize_numeric(pb_mat): assert len(pb_mat.numeric.data) == pb_mat.rows * pb_mat.cols, "Unexpected number of elements in serialized matrix" ncols = pb_mat.cols array = [] row = [] for cplx in pb_mat.numeric.data: row.append(complex(cplx.real_value, cplx.imaginary_value)) if len(row) == ncols: array.append(row) row = [] return array def _deserialize_symbolic(pb_mat): assert len(pb_mat.symbolic.data) == pb_mat.rows * pb_mat.cols, "Unexpected number of elements in serialized matrix" ncols = pb_mat.cols array = [] row = [] for param in pb_mat.symbolic.data: row.append(deserialize_parameter(param)) if len(row) == ncols: array.append(row) row = [] return array def deserialize_pb_matrix(pb_mat: pb.Matrix) -> Matrix: if pb_mat.HasField('numeric'): mat_data = _deserialize_numeric(pb_mat) return Matrix(mat_data) else: mat_data = _deserialize_symbolic(pb_mat) return Matrix(mat_data, use_symbolic=True) ================================================ FILE: perceval/serialization/_parameter_serialization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sympy as sp from perceval.utils import Parameter, Expression from perceval.serialization import _schema_circuit_pb2 as pb def serialize_parameter(param: Parameter | float): """ Serialize a numerical value, a Parameter or an Expression as a protobuf "Parameter" message :param param: Different cases * A numerical value is serialized as an anonymous Parameter having the given value * A Parameter is serialized as is, with at least a symbol name, or a numerical value * An Expression serializes all its sub-Parameters internally + its sympy parsable expression """ pb_param = pb.Parameter() if isinstance(param, float): pb_param.real_value = param return pb_param if param._symbol is not None: pb_param.name = str(param._symbol) if param.defined: pb_param.real_value = float(param) elif param._is_expression: serial_param_list = [] for internal_param in param.parameters: serial_param_list.append(serialize_parameter(internal_param)) pb_param.expr_parameters.extend(serial_param_list) pb_param.expression = param.name else: # If param has no value and is not an expression, it is defined only by a symbol pb_param.symbol = str(param._symbol) return pb_param def deserialize_parameter(serial_param: pb.Parameter, known_params = None): """ Deserialize a protobuf "Parameter" message to a Parameter, an Expression or a numerical value. :param serial_param: Protobuf Parameter message :param known_params: A dictionary of already known parameters, used to reuse the same Parameter instance, when the same parameter name is met more than once. """ if known_params is None: known_params = {} t = serial_param.WhichOneof('type') if t == 'real_value': if serial_param.name: if serial_param.name in known_params: if float(known_params[serial_param.name]) != serial_param.real_value: raise ValueError(f"Parameter {serial_param.name} has multiple values ({float(known_params[serial_param.name])} and {serial_param.real_value})") return known_params[serial_param.name] p = Parameter(serial_param.name) p.set_value(serial_param.real_value) known_params[serial_param.name] = p return p return serial_param.real_value elif t == 'symbol': if serial_param.symbol in known_params: return known_params[serial_param.symbol] p = Parameter(serial_param.symbol) known_params[serial_param.name] = p return p elif t == 'expression': if serial_param.expr_parameters: internal_params = set() for internal_serial_param in serial_param.expr_parameters: internal_params.add(deserialize_parameter(internal_serial_param, known_params)) return Expression(serial_param.name, internal_params) return sp.S(serial_param.expression) ================================================ FILE: perceval/serialization/_port_deserialization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from base64 import b64decode import perceval.components.port as port from perceval.utils import Encoding from perceval.serialization import _schema_circuit_pb2 as pb def deserialize_herald(serial_herald) -> port.Herald: if not isinstance(serial_herald, pb.Herald): pb_binary_repr = serial_herald serial_herald = pb.Herald() if isinstance(pb_binary_repr, bytes): serial_herald.ParseFromString(pb_binary_repr) else: serial_herald.ParseFromString(b64decode(pb_binary_repr)) herald = port.Herald(serial_herald.value) herald._autogenerated_name = serial_herald.autogenerated_name if serial_herald.name: # Default '' herald.name = serial_herald.name return herald def deserialize_port(serial_port) -> port.Port: if not isinstance(serial_port, pb.Port): pb_binary_repr = serial_port serial_port = pb.Port() if isinstance(pb_binary_repr, bytes): serial_port.ParseFromString(pb_binary_repr) else: serial_port.ParseFromString(b64decode(pb_binary_repr)) encoding = Encoding(serial_port.encoding) return port.Port(encoding, serial_port.name) ================================================ FILE: perceval/serialization/_schema_circuit_pb2.py ================================================ # -*- coding: utf-8 -*- # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # flake8: noqa # Generated by the protocol buffer compiler. DO NOT EDIT! # source: perceval_circuit.proto """Generated protocol buffer code.""" from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1asrc/perceval_circuit.proto\x12\x0fperceval.schema\"<\n\rComplexDouble\x12\x12\n\nreal_value\x18\x01 \x01(\x01\x12\x17\n\x0fimaginary_value\x18\x02 \x01(\x01\"\x94\x01\n\tParameter\x12\x14\n\nreal_value\x18\x01 \x01(\x01H\x00\x12\x10\n\x06symbol\x18\x02 \x01(\tH\x00\x12\x14\n\nexpression\x18\x03 \x01(\tH\x00\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x33\n\x0f\x65xpr_parameters\x18\x05 \x03(\x0b\x32\x1a.perceval.schema.ParameterB\x06\n\x04type\"<\n\x0cMatrixDouble\x12,\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32\x1e.perceval.schema.ComplexDouble\":\n\x0eMatrixSymbolic\x12(\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32\x1a.perceval.schema.Parameter\"\x93\x01\n\x06Matrix\x12\x0c\n\x04rows\x18\x01 \x01(\x05\x12\x0c\n\x04\x63ols\x18\x02 \x01(\x05\x12\x30\n\x07numeric\x18\x03 \x01(\x0b\x32\x1d.perceval.schema.MatrixDoubleH\x00\x12\x33\n\x08symbolic\x18\x04 \x01(\x0b\x32\x1f.perceval.schema.MatrixSymbolicH\x00\x42\x06\n\x04\x64\x61ta\"\xe5\x07\n\tComponent\x12\x15\n\rstarting_mode\x18\x01 \x01(\x05\x12\x0e\n\x06n_mode\x18\x02 \x01(\x05\x12\x10\n\x08offset_x\x18\x03 \x01(\x01\x12\x12\n\nannotation\x18\x04 \x01(\t\x12+\n\x07\x63ircuit\x18\n \x01(\x0b\x32\x18.perceval.schema.CircuitH\x00\x12\x36\n\rphase_shifter\x18\x0b \x01(\x0b\x32\x1d.perceval.schema.PhaseShifterH\x00\x12\x36\n\rbeam_splitter\x18\x0c \x01(\x0b\x32\x1d.perceval.schema.BeamSplitterH\x00\x12\x33\n\x0bpermutation\x18\x0e \x01(\x0b\x32\x1c.perceval.schema.PermutationH\x00\x12+\n\x07unitary\x18\x0f \x01(\x0b\x32\x18.perceval.schema.UnitaryH\x00\x12\x30\n\nwave_plate\x18\x10 \x01(\x0b\x32\x1a.perceval.schema.WavePlateH\x00\x12\x35\n\x0fhalf_wave_plate\x18\x11 \x01(\x0b\x32\x1a.perceval.schema.WavePlateH\x00\x12\x38\n\x12quarter_wave_plate\x18\x12 \x01(\x0b\x32\x1a.perceval.schema.WavePlateH\x00\x12\x44\n\x14polarization_rotator\x18\x13 \x01(\x0b\x32$.perceval.schema.PolarizationRotatorH\x00\x12\x30\n\ntime_delay\x18\x14 \x01(\x0b\x32\x1a.perceval.schema.TimeDelayH\x00\x12I\n\x17polarized_beam_splitter\x18\x15 \x01(\x0b\x32&.perceval.schema.PolarizedBeamSplitterH\x00\x12+\n\x07\x62\x61rrier\x18\x16 \x01(\x0b\x32\x18.perceval.schema.BarrierH\x00\x12\x34\n\x0closs_channel\x18\x17 \x01(\x0b\x32\x1c.perceval.schema.LossChannelH\x00\x12\x41\n\x13\x66\x66_circuit_provider\x18\x18 \x01(\x0b\x32\".perceval.schema.FFCircuitProviderH\x00\x12:\n\x0f\x66\x66_configurator\x18\x19 \x01(\x0b\x32\x1f.perceval.schema.FFConfiguratorH\x00\x12<\n\x10\x63ompiled_circuit\x18\x1a \x01(\x0b\x32 .perceval.schema.CompiledCircuitH\x00\x42\x06\n\x04type\"f\n\x0cPhaseShifter\x12\'\n\x03phi\x18\x01 \x01(\x0b\x32\x1a.perceval.schema.Parameter\x12-\n\tmax_error\x18\x02 \x01(\x0b\x32\x1a.perceval.schema.Parameter\"\xcc\x02\n\x0c\x42\x65\x61mSplitter\x12<\n\nconvention\x18\x01 \x01(\x0e\x32(.perceval.schema.BeamSplitter.Convention\x12)\n\x05theta\x18\x02 \x01(\x0b\x32\x1a.perceval.schema.Parameter\x12*\n\x06phi_tl\x18\x03 \x01(\x0b\x32\x1a.perceval.schema.Parameter\x12*\n\x06phi_bl\x18\x04 \x01(\x0b\x32\x1a.perceval.schema.Parameter\x12*\n\x06phi_tr\x18\x05 \x01(\x0b\x32\x1a.perceval.schema.Parameter\x12*\n\x06phi_br\x18\x06 \x01(\x0b\x32\x1a.perceval.schema.Parameter\"#\n\nConvention\x12\x06\n\x02Rx\x10\x00\x12\x06\n\x02Ry\x10\x01\x12\x05\n\x01H\x10\x02\"\x17\n\x15PolarizedBeamSplitter\"W\n\x07Unitary\x12$\n\x03mat\x18\x01 \x01(\x0b\x32\x17.perceval.schema.Matrix\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x18\n\x10use_polarization\x18\x03 \x01(\x08\"#\n\x0bPermutation\x12\x14\n\x0cpermutations\x18\x01 \x03(\x05\"_\n\tWavePlate\x12)\n\x05\x64\x65lta\x18\x01 \x01(\x0b\x32\x1a.perceval.schema.Parameter\x12\'\n\x03xsi\x18\x02 \x01(\x0b\x32\x1a.perceval.schema.Parameter\"@\n\x13PolarizationRotator\x12)\n\x05\x64\x65lta\x18\x01 \x01(\x0b\x32\x1a.perceval.schema.Parameter\"3\n\tTimeDelay\x12&\n\x02\x64t\x18\x01 \x01(\x0b\x32\x1a.perceval.schema.Parameter\"7\n\x0bLossChannel\x12(\n\x04loss\x18\x01 \x01(\x0b\x32\x1a.perceval.schema.Parameter\"\x1a\n\x07\x42\x61rrier\x12\x0f\n\x07visible\x18\x01 \x01(\x08\"k\n\x07\x43ircuit\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06n_mode\x18\x02 \x01(\x05\x12\x12\n\nannotation\x18\x03 \x01(\t\x12.\n\ncomponents\x18\x04 \x03(\x0b\x32\x1a.perceval.schema.Component\"F\n\rBSLayeredPPNR\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\tbs_layers\x18\x02 \x01(\x05\x12\x14\n\x0creflectivity\x18\x03 \x01(\x01\"Z\n\x08\x44\x65tector\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07n_wires\x18\x02 \x01(\x05\x12\x16\n\x0emax_detections\x18\x03 \x01(\x05\x12\x17\n\x0fwire_efficiency\x18\x04 \x01(\x01\"r\n\tIDetector\x12.\n\x04ppnr\x18\x01 \x01(\x0b\x32\x1e.perceval.schema.BSLayeredPPNRH\x00\x12-\n\x08\x64\x65tector\x18\x02 \x01(\x0b\x32\x19.perceval.schema.DetectorH\x00\x42\x06\n\x04type\"&\n\x04Port\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x10\n\x08\x65ncoding\x18\x02 \x01(\x05\"A\n\x06Herald\x12\x1a\n\x12\x61utogenerated_name\x18\x01 \x01(\x08\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\x05\"a\n\x05\x41Port\x12%\n\x04port\x18\x01 \x01(\x0b\x32\x15.perceval.schema.PortH\x00\x12)\n\x06herald\x18\x02 \x01(\x0b\x32\x17.perceval.schema.HeraldH\x00\x42\x06\n\x04type\"\xdf\x04\n\nExperiment\x12\x13\n\x0binput_state\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0bnoise_model\x18\x03 \x01(\t\x12\x13\n\x0bpost_select\x18\x04 \x01(\t\x12@\n\x0binput_ports\x18\x05 \x03(\x0b\x32+.perceval.schema.Experiment.InputPortsEntry\x12\x42\n\x0coutput_ports\x18\x06 \x03(\x0b\x32,.perceval.schema.Experiment.OutputPortsEntry\x12=\n\tdetectors\x18\x07 \x03(\x0b\x32*.perceval.schema.Experiment.DetectorsEntry\x12\x0e\n\x06n_mode\x18\x08 \x01(\x05\x12.\n\ncomponents\x18\t \x03(\x0b\x32\x1a.perceval.schema.Component\x12\x1a\n\x12min_photons_filter\x18\n \x01(\x05\x1aI\n\x0fInputPortsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.perceval.schema.APort:\x02\x38\x01\x1aJ\n\x10OutputPortsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.perceval.schema.APort:\x02\x38\x01\x1aL\n\x0e\x44\x65tectorsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x1a.perceval.schema.IDetector:\x02\x38\x01\"T\n\x0f\x43ompiledCircuit\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06n_mode\x18\x02 \x01(\x05\x12\x12\n\nparameters\x18\x03 \x03(\x01\x12\x0f\n\x07version\x18\x04 \x01(\t\"}\n\x13\x43ircuitOrExperiment\x12+\n\x07\x63ircuit\x18\x01 \x01(\x0b\x32\x18.perceval.schema.CircuitH\x00\x12\x31\n\nexperiment\x18\x02 \x01(\x0b\x32\x1b.perceval.schema.ExperimentH\x00\x42\x06\n\x04type\"\xe2\x02\n\x11\x46\x46\x43ircuitProvider\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x1a\n\x12\x62lock_circuit_size\x18\x03 \x01(\x08\x12+\n\x07\x63ircuit\x18\n \x01(\x0b\x32\x18.perceval.schema.CircuitH\x00\x12\x31\n\nexperiment\x18\x0b \x01(\x0b\x32\x1b.perceval.schema.ExperimentH\x00\x12G\n\x0b\x63onfig_circ\x18\x14 \x03(\x0b\x32\x32.perceval.schema.FFCircuitProvider.ConfigCircEntry\x1aW\n\x0f\x43onfigCircEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x33\n\x05value\x18\x02 \x01(\x0b\x32$.perceval.schema.CircuitOrExperiment:\x02\x38\x01\x42\x11\n\x0f\x64\x65\x66\x61ult_circuit\"\x7f\n\x0eVariableValues\x12=\n\x07mapping\x18\x01 \x03(\x0b\x32,.perceval.schema.VariableValues.MappingEntry\x1a.\n\x0cMappingEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x02:\x02\x38\x01\"\xc9\x02\n\x0e\x46\x46\x43onfigurator\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x1a\n\x12\x62lock_circuit_size\x18\x03 \x01(\x08\x12\x34\n\x12\x63ontrolled_circuit\x18\x04 \x01(\x0b\x32\x18.perceval.schema.Circuit\x12\x37\n\x0e\x64\x65\x66\x61ult_config\x18\x05 \x01(\x0b\x32\x1f.perceval.schema.VariableValues\x12=\n\x07\x63onfigs\x18\x06 \x03(\x0b\x32,.perceval.schema.FFConfigurator.ConfigsEntry\x1aO\n\x0c\x43onfigsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.perceval.schema.VariableValues:\x02\x38\x01\x62\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'perceval_circuit_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None _EXPERIMENT_INPUTPORTSENTRY._options = None _EXPERIMENT_INPUTPORTSENTRY._serialized_options = b'8\001' _EXPERIMENT_OUTPUTPORTSENTRY._options = None _EXPERIMENT_OUTPUTPORTSENTRY._serialized_options = b'8\001' _EXPERIMENT_DETECTORSENTRY._options = None _EXPERIMENT_DETECTORSENTRY._serialized_options = b'8\001' _FFCIRCUITPROVIDER_CONFIGCIRCENTRY._options = None _FFCIRCUITPROVIDER_CONFIGCIRCENTRY._serialized_options = b'8\001' _VARIABLEVALUES_MAPPINGENTRY._options = None _VARIABLEVALUES_MAPPINGENTRY._serialized_options = b'8\001' _FFCONFIGURATOR_CONFIGSENTRY._options = None _FFCONFIGURATOR_CONFIGSENTRY._serialized_options = b'8\001' _COMPLEXDOUBLE._serialized_start=47 _COMPLEXDOUBLE._serialized_end=107 _PARAMETER._serialized_start=110 _PARAMETER._serialized_end=258 _MATRIXDOUBLE._serialized_start=260 _MATRIXDOUBLE._serialized_end=320 _MATRIXSYMBOLIC._serialized_start=322 _MATRIXSYMBOLIC._serialized_end=380 _MATRIX._serialized_start=383 _MATRIX._serialized_end=530 _COMPONENT._serialized_start=533 _COMPONENT._serialized_end=1530 _PHASESHIFTER._serialized_start=1532 _PHASESHIFTER._serialized_end=1634 _BEAMSPLITTER._serialized_start=1637 _BEAMSPLITTER._serialized_end=1969 _BEAMSPLITTER_CONVENTION._serialized_start=1934 _BEAMSPLITTER_CONVENTION._serialized_end=1969 _POLARIZEDBEAMSPLITTER._serialized_start=1971 _POLARIZEDBEAMSPLITTER._serialized_end=1994 _UNITARY._serialized_start=1996 _UNITARY._serialized_end=2083 _PERMUTATION._serialized_start=2085 _PERMUTATION._serialized_end=2120 _WAVEPLATE._serialized_start=2122 _WAVEPLATE._serialized_end=2217 _POLARIZATIONROTATOR._serialized_start=2219 _POLARIZATIONROTATOR._serialized_end=2283 _TIMEDELAY._serialized_start=2285 _TIMEDELAY._serialized_end=2336 _LOSSCHANNEL._serialized_start=2338 _LOSSCHANNEL._serialized_end=2393 _BARRIER._serialized_start=2395 _BARRIER._serialized_end=2421 _CIRCUIT._serialized_start=2423 _CIRCUIT._serialized_end=2530 _BSLAYEREDPPNR._serialized_start=2532 _BSLAYEREDPPNR._serialized_end=2602 _DETECTOR._serialized_start=2604 _DETECTOR._serialized_end=2694 _IDETECTOR._serialized_start=2696 _IDETECTOR._serialized_end=2810 _PORT._serialized_start=2812 _PORT._serialized_end=2850 _HERALD._serialized_start=2852 _HERALD._serialized_end=2917 _APORT._serialized_start=2919 _APORT._serialized_end=3016 _EXPERIMENT._serialized_start=3019 _EXPERIMENT._serialized_end=3626 _EXPERIMENT_INPUTPORTSENTRY._serialized_start=3399 _EXPERIMENT_INPUTPORTSENTRY._serialized_end=3472 _EXPERIMENT_OUTPUTPORTSENTRY._serialized_start=3474 _EXPERIMENT_OUTPUTPORTSENTRY._serialized_end=3548 _EXPERIMENT_DETECTORSENTRY._serialized_start=3550 _EXPERIMENT_DETECTORSENTRY._serialized_end=3626 _COMPILEDCIRCUIT._serialized_start=3628 _COMPILEDCIRCUIT._serialized_end=3712 _CIRCUITOREXPERIMENT._serialized_start=3714 _CIRCUITOREXPERIMENT._serialized_end=3839 _FFCIRCUITPROVIDER._serialized_start=3842 _FFCIRCUITPROVIDER._serialized_end=4196 _FFCIRCUITPROVIDER_CONFIGCIRCENTRY._serialized_start=4090 _FFCIRCUITPROVIDER_CONFIGCIRCENTRY._serialized_end=4177 _VARIABLEVALUES._serialized_start=4198 _VARIABLEVALUES._serialized_end=4325 _VARIABLEVALUES_MAPPINGENTRY._serialized_start=4279 _VARIABLEVALUES_MAPPINGENTRY._serialized_end=4325 _FFCONFIGURATOR._serialized_start=4328 _FFCONFIGURATOR._serialized_end=4657 _FFCONFIGURATOR_CONFIGSENTRY._serialized_start=4578 _FFCONFIGURATOR_CONFIGSENTRY._serialized_end=4657 # @@protoc_insertion_point(module_scope) ================================================ FILE: perceval/serialization/_serialized_containers.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sys from .serialize import serialize from multipledispatch import dispatch class SerializedDict(dict): """ Class that mimics a python dict, but internally stores serialized versions of the perceval objects given to it, so that json.dumps can always be called on this class (assuming this only contains python native and perceval objects) The compression parameter is always the default one. If another value is wanted, call :code:`serialize` with the wanted compression parameter before setting the key or value. Retrieving the value of a key will always return the serialized value. """ def __init__(self, *args, **kwargs): d = dict(*args, **kwargs) for key, value in d.items(): self[key] = value def __setitem__(self, key, value): return super().__setitem__(serialize(key), make_serialized(value)) def setdefault(self, key, default = None, /): if key not in self: self[key] = default return self[key] def __delitem__(self, key): return super().__delitem__(serialize(key)) def __getitem__(self, item): return super().__getitem__(serialize(item)) def get(self, key, default=None): return super().get(serialize(key), default) def pop(self, key, default=None): return super().pop(serialize(key), default) @staticmethod def fromkeys(*args, **kwargs): return SerializedDict(dict.fromkeys(*args, **kwargs)) def __contains__(self, item): return super().__contains__(serialize(item)) def update(self, E=None, **F): if E: if hasattr(E, 'keys'): for key in E.keys(): self[key] = E[key] else: for key, value in E: self[key] = value for key, value in F.items(): self[key] = value def __ior__(self, other): self.update(**other) return self def __or__(self, other): temp = SerializedDict(self) temp |= other return temp class SerializedList(list): """ Class that mimics a python list, but internally stores serialized versions of the perceval objects given to it, so that json.dumps can always be called on this class (assuming this only contains python native and perceval objects) The compression parameter is always the default one. If another value is wanted, call :code:`serialize` with the wanted compression parameter before setting the key or value. Retrieving the value of a key will always return the serialized value. """ def __init__(self, seq=()): for value in seq: self.append(value) def __setitem__(self, key, value): return super().__setitem__(key, make_serialized(value)) def append(self, value): return super().append(make_serialized(value)) def count(self, __value): return super().count(serialize(__value)) def extend(self, __iterable): return super().extend(SerializedList(__iterable)) def index(self, __value, __start = 0, __stop = sys.maxsize): return super().index(serialize(__value), __start, __stop) def insert(self, __index, value): return super().insert(__index, make_serialized(value)) def remove(self, value): return super().remove(serialize(value)) def __add__(self, other): return super().__add__(SerializedList(other)) def __iadd__(self, other): return super().__iadd__(SerializedList(other)) def __contains__(self, item): return super().__contains__(serialize(item)) def __delitem__(self, key): return super().__delitem__(serialize(key)) @dispatch(dict) def make_serialized(d: dict): if isinstance(d, SerializedDict): return d # We want to avoid a copy return SerializedDict(d) @dispatch(list) def make_serialized(l: list): if isinstance(l, SerializedList): return l return SerializedList(l) @dispatch(object) def make_serialized(o: object): return serialize(o) ================================================ FILE: perceval/serialization/_state_serialization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.utils.states import BasicState, StateVector, BSSamples from perceval.utils import simple_float from ast import literal_eval import re def serialize_state(state: BasicState) -> str: return str(state) def deserialize_state(serial_bs) -> BasicState: return BasicState(serial_bs) def deserialize_state_list(states): state_list = literal_eval(states) return [deserialize_state(s) for s in state_list] def serialize_statevector(sv: StateVector) -> str: sv.normalize() ls = [] for key, value in sv: real = simple_float(value.real, nsimplify=False)[1] imag = simple_float(value.imag, nsimplify=False)[1] ls.append("(%s,%s)*%s" % (real, imag, str(key))) return "+".join(ls) def deserialize_statevector(s) -> StateVector: sv = StateVector() for c in s.split("+"): m = re.match(r"\((.*),(.*)\)\*(.*)$", c) assert m, "invalid state vector serialization: %s" % s sv += BasicState(m.group(3)) * (float(m.group(1)) + 1j * float(m.group(2))) return sv def serialize_bssamples(bss: BSSamples) -> str: order = [0]*len(bss) mapping = {} index = 0 for idx, bs in enumerate(bss): if bs not in mapping: mapping[bs] = index order[idx] = index index += 1 else: order[idx] = mapping[bs] return ';'.join([serialize_state(bs) for bs in mapping.keys()]) + '/' + ';'.join([str(i) for i in order]) def deserialize_bssamples(serialized_bss: str) -> BSSamples: parts = serialized_bss.split('/') assert len(parts) == 2, f"Bad serialized BSSamples: {serialized_bss}" if not parts[0]: return BSSamples() bs_set = [deserialize_state(bs) for bs in parts[0].split(';')] order = [int(x) for x in parts[1].split(';')] output = BSSamples() for index in order: output.append(bs_set[index]) return output ================================================ FILE: perceval/serialization/deserialize.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from base64 import b64decode from os import path import json from zlib import decompress from perceval.components import Circuit, BSLayeredPPNR, Detector, AComponent, Experiment, PortLocation, Port, Herald, \ IDetector, AFFConfigurator, CompiledCircuit from perceval.utils import Matrix, BSDistribution, SVDistribution, BasicState, BSCount, NoiseModel, PostSelect from perceval.utils.logging import get_logger, channel from perceval.serialization import _matrix_serialization, deserialize_state, _detector_serialization from ._component_deserialization import deserialize_cc from ._port_deserialization import deserialize_herald, deserialize_port from ._constants import ( SEP, PCVL_PREFIX, ZIP_PREFIX, BS_TAG, SV_TAG, SVD_TAG, BSD_TAG, BSC_TAG, BSS_TAG, MATRIX_TAG, CIRCUIT_TAG, NOISE_TAG, POSTSELECT_TAG, BS_LAYERED_DETECTOR_TAG, DETECTOR_TAG, COMPONENT_TAG, HERALD_TAG, PORT_TAG, VALUE_NOT_SET, EXPERIMENT_TAG, COMPILED_CIRCUIT_TAG, ) from ._state_serialization import deserialize_statevector, deserialize_bssamples from . import _component_deserialization as _cd from . import _schema_circuit_pb2 as pb def deserialize_float(floatstring): return float(floatstring) def deserialize_matrix(pb_mat: str | pb.Matrix) -> Matrix: if not isinstance(pb_mat, pb.Matrix): pb_binary_repr = pb_mat pb_mat = pb.Matrix() if isinstance(pb_binary_repr, bytes): pb_mat.ParseFromString(pb_binary_repr) else: pb_mat.ParseFromString(b64decode(pb_binary_repr)) return _matrix_serialization.deserialize_pb_matrix(pb_mat) def matrix_from_file(filepath: str) -> Matrix: """ Deserialize a matrix from a binary file """ if not path.isfile(filepath): raise FileNotFoundError(f'No file at path {filepath}') with open(filepath, 'rb') as f: return deserialize_matrix(f.read()) def deserialize_circuit(pb_circ: str | bytes | pb.Circuit, known_params: dict = None) -> Circuit: if not isinstance(pb_circ, pb.Circuit): pb_binary_repr = pb_circ pb_circ = pb.Circuit() if isinstance(pb_binary_repr, bytes): pb_circ.ParseFromString(pb_binary_repr) else: pb_circ.ParseFromString(b64decode(pb_binary_repr)) builder = CircuitBuilder(pb_circ.n_mode, pb_circ.name, known_params) for pb_c in pb_circ.components: builder.add(pb_c) return builder.retrieve() def circuit_from_file(filepath: str) -> Circuit: """ Deserialize a circuit from a binary file """ if not path.isfile(filepath): raise FileNotFoundError(f'No file at path {filepath}') with open(filepath, 'rb') as f: return deserialize_circuit(f.read()) def deserialize_component(pb_c: pb.Component, known_params: dict = None) -> AComponent: if not isinstance(pb_c, pb.Component): pb_binary_repr = pb_c pb_c = pb.Component() if isinstance(pb_binary_repr, bytes): pb_c.ParseFromString(pb_binary_repr) else: pb_c.ParseFromString(b64decode(pb_binary_repr)) return CircuitBuilder.deserialize(pb_c, known_params) def deserialize_experiment(pb_e: pb.Experiment, known_params: dict = None) -> Experiment: if not isinstance(pb_e, pb.Experiment): pb_binary_repr = pb_e pb_e = pb.Experiment() if isinstance(pb_binary_repr, bytes): pb_e.ParseFromString(pb_binary_repr) else: pb_e.ParseFromString(b64decode(pb_binary_repr)) builder = ExperimentBuilder(pb_e, known_params) return builder.resolve() def deserialize_compiled_circuit(pb_cc: pb.CompiledCircuit, known_params: dict = None) -> CompiledCircuit: if not isinstance(pb_cc, pb.CompiledCircuit): pb_binary_repr = pb_cc pb_cc = pb.CompiledCircuit() if isinstance(pb_binary_repr, bytes): pb_cc.ParseFromString(pb_binary_repr) else: pb_cc.ParseFromString(b64decode(pb_binary_repr)) return deserialize_cc(pb_cc, known_params) def deserialize_svdistribution(serial_svd) -> SVDistribution: assert serial_svd[0] == '{' and serial_svd[-1] == '}', "Invalid serialized SVDistribution" if len(serial_svd) == 2: return SVDistribution() svd = SVDistribution() for s in serial_svd[1:-1].split(";"): k, v = s.split("=") svd[deserialize_statevector(k)] = float(v) return svd def deserialize_bsdistribution(serial_bsd) -> BSDistribution: assert serial_bsd[0] == '{' and serial_bsd[-1] == '}', "Invalid serialized BSDistribution" if len(serial_bsd) == 2: return BSDistribution() bsd = BSDistribution() for s in serial_bsd[1:-1].split(";"): k, v = s.split("=") bsd[deserialize_state(k)] = float(v) return bsd def deserialize_bscount(serial_bsc) -> BSCount: assert serial_bsc[0] == '{' and serial_bsc[-1] == '}', "Invalid serialized BSCount" if len(serial_bsc) == 2: return BSCount() bsc = BSCount() for s in serial_bsc[1:-1].split(";"): k, v = s.split("=") bsc[deserialize_state(k)] = int(v) return bsc def deserialize_noise_model(serial_nm: str) -> NoiseModel: return NoiseModel(**json.loads(serial_nm)) def deserialize_postselect(serial_ps: str) -> PostSelect: return PostSelect(serial_ps) def deserialize_bs_layered_detector(pb_detect: str | bytes | pb.BSLayeredPPNR) -> BSLayeredPPNR: if not isinstance(pb_detect, pb.BSLayeredPPNR): pb_binary_repr = pb_detect pb_detect = pb.BSLayeredPPNR() if isinstance(pb_binary_repr, bytes): pb_detect.ParseFromString(pb_binary_repr) else: pb_detect.ParseFromString(b64decode(pb_binary_repr)) return _detector_serialization.deserialize_bs_layer(pb_detect) def deserialize_detector(pb_detect: str | bytes | pb.Detector) -> Detector: if not isinstance(pb_detect, pb.Detector): pb_binary_repr = pb_detect pb_detect = pb.Detector() if isinstance(pb_binary_repr, bytes): pb_detect.ParseFromString(pb_binary_repr) else: pb_detect.ParseFromString(b64decode(pb_binary_repr)) return _detector_serialization.deserialize_detector(pb_detect) # Known deserializer functions DESERIALIZER = { BS_TAG: BasicState, SV_TAG: deserialize_statevector, SVD_TAG: deserialize_svdistribution, BSD_TAG: deserialize_bsdistribution, BSC_TAG: deserialize_bscount, BSS_TAG: deserialize_bssamples, MATRIX_TAG: deserialize_matrix, CIRCUIT_TAG: deserialize_circuit, NOISE_TAG: deserialize_noise_model, POSTSELECT_TAG: deserialize_postselect, DETECTOR_TAG: deserialize_detector, BS_LAYERED_DETECTOR_TAG: deserialize_bs_layered_detector, COMPONENT_TAG: deserialize_component, HERALD_TAG: deserialize_herald, PORT_TAG: deserialize_port, EXPERIMENT_TAG: deserialize_experiment, COMPILED_CIRCUIT_TAG: deserialize_compiled_circuit, } def deserialize(obj, strict=True): if isinstance(obj, bytes): raise TypeError("Generic deserialize function does not handle binary representation. " "Use specialized functions (e.g. deserialize_circuit) instead.") if isinstance(obj, dict): r = {} for k, v in obj.items(): r[deserialize(k, strict=strict)] = deserialize(v, strict=strict) elif isinstance(obj, list): r = [] for k in obj: r.append(deserialize(k, strict=strict)) elif isinstance(obj, str) and obj.startswith(PCVL_PREFIX): if obj.startswith(ZIP_PREFIX): # if zip found -> update obj # STEPS: remove prefix -> decode b64 encoding -> decompress -> decode utf-8 (byte-> str) obj = b64decode(obj[len(ZIP_PREFIX):]) obj = decompress(obj).decode('utf-8') lp = len(PCVL_PREFIX) p = obj[lp:].find(SEP) class_obj = obj[lp:p+lp] serial_obj = obj[p+lp+1:] def serializer_not_implemented(_: str): if strict: raise NotImplementedError(f"No serializer found for {class_obj}") get_logger().warn(f"Unknown perceval class {class_obj}, leaving it serialized; Consider upgrading perceval", channel.user) return obj deserialize_func = DESERIALIZER.get(class_obj, serializer_not_implemented) r = deserialize_func(serial_obj) else: r = obj return r def deserialize_file(filepath: str, strict=True): """ Agnosticly deserialize any supported type from a text file. """ if not path.isfile(filepath): raise FileNotFoundError(f'No file at path {filepath}') with open(filepath, 'r') as f: return deserialize(json.loads(f.read()), strict=strict) class CircuitBuilder: deserialize_fn = { 'circuit': deserialize_circuit, 'beam_splitter': _cd.deserialize_bs, 'phase_shifter': _cd.deserialize_ps, 'permutation': _cd.deserialize_perm, 'unitary': _cd.deserialize_unitary, 'wave_plate': _cd.deserialize_wp, 'quarter_wave_plate': _cd.deserialize_qwp, 'half_wave_plate': _cd.deserialize_hwp, 'time_delay': _cd.deserialize_dt, 'polarization_rotator': _cd.deserialize_pr, 'polarized_beam_splitter': _cd.deserialize_pbs, 'loss_channel': _cd.deserialize_lc, 'compiled_circuit': _cd.deserialize_cc } deserialize_fn_m = { # Deserialization functions requiring m value 'barrier': _cd.deserialize_barrier, 'ff_configurator': _cd.deserialize_ff_configurator, 'ff_circuit_provider': _cd.deserialize_ff_circuit_provider, } def __init__(self, m: int, name: str, params: dict): if not name: name = None self._circuit = Circuit(m=m, name=name) self._params = params or dict() def add(self, serial_comp): component = self.deserialize(serial_comp, self._params) self._circuit.add(serial_comp.starting_mode, component, merge=False) def retrieve(self): return self._circuit @staticmethod def deserialize(serial_comp, params): component = None t = serial_comp.WhichOneof('type') serial_sub_comp = getattr(serial_comp, t) # find the correct deserialization function and use it if t in CircuitBuilder.deserialize_fn: func = CircuitBuilder.deserialize_fn[t] component = func(serial_sub_comp, params) elif t in CircuitBuilder.deserialize_fn_m: func = CircuitBuilder.deserialize_fn_m[t] component = func(serial_comp.n_mode, serial_sub_comp, params) if component is None: raise NotImplementedError(f'Component could not be deserialized (type = {t})') return component class ExperimentBuilder: deserialize_port_fn = { "port": deserialize_port, "herald": deserialize_herald, } deserialize_detector_fn = { "detector": deserialize_detector, "ppnr": deserialize_bs_layered_detector, } def __init__(self, pb_e: pb.Experiment, params: dict): self._pb_e = pb_e self._params = params or dict() def deserialize_ports(self, experiment, serialized_port_map, location: PortLocation): for i, serial_port in sorted(serialized_port_map.items()): # Sorted needed for the heralds autogenerated names t = serial_port.WhichOneof('type') serial_sub_comp = getattr(serial_port, t) # find the correct deserialization function and use it func = self.deserialize_port_fn[t] port = func(serial_sub_comp) if isinstance(port, Port): experiment.add_port(i, port, location=location) elif isinstance(port, Herald): experiment.add_herald(i, port.expected, port.user_given_name, location) def resolve(self): name = self._pb_e.name m = self._pb_e.n_mode if not name: name = None if m == 0: m = None experiment = Experiment(m_circuit=m, name=name) noise_model_str = self._pb_e.noise_model if noise_model_str: experiment.noise = deserialize(noise_model_str) # Includes the heralded modes --> needed before adding the heralds input_state_str = self._pb_e.input_state if input_state_str: input_state = deserialize(input_state_str) experiment.with_input(input_state) min_photons_filter = self._pb_e.min_photons_filter if min_photons_filter != VALUE_NOT_SET: experiment.min_detected_photons_filter(min_photons_filter) detectors: list[IDetector | None] = [None] * experiment.m injected_detectors: list[bool] = [False] * experiment.m for i, serial_detector in self._pb_e.detectors.items(): t = serial_detector.WhichOneof('type') serial_sub_comp = getattr(serial_detector, t) # find the correct deserialization function and use it func = self.deserialize_detector_fn[t] detector = func(serial_sub_comp) detectors[i] = detector # Needed before adding the heralds for serial_comp in self._pb_e.components: component = CircuitBuilder.deserialize(serial_comp, self._params) if isinstance(component, AFFConfigurator): for i in range(serial_comp.starting_mode, serial_comp.starting_mode + component.m): if not injected_detectors[i]: experiment._components.append(((i,), detectors[i])) injected_detectors[i] = True experiment._components.append((tuple(i for i in range(serial_comp.starting_mode, serial_comp.starting_mode + component.m)), component)) experiment._is_unitary = False experiment._has_feedforward = True else: experiment.add(serial_comp.starting_mode, component) # Add detectors for i, d in enumerate(detectors): if d is not None: experiment.add(i, d) self.deserialize_ports(experiment, self._pb_e.output_ports, PortLocation.OUTPUT) self.deserialize_ports(experiment, self._pb_e.input_ports, PortLocation.INPUT) # Blocks adding new components --> Needed after adding the components post_select_str = self._pb_e.post_select if post_select_str: experiment.set_postselection(deserialize(post_select_str)) return experiment ================================================ FILE: perceval/serialization/serialize.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from multipledispatch import dispatch from zlib import compress as zlib_compress from ._constants import * from ._detector_serialization import serialize_bs_layer, serialize_detector from ._experiment_serialization import serialize_experiment from ._matrix_serialization import serialize_matrix from ._circuit_serialization import serialize_circuit, serialize_component, serialize_herald, serialize_port, \ serialize_compiled_circuit from ._state_serialization import serialize_state, serialize_statevector, serialize_bssamples from perceval.components import ACircuit, BSLayeredPPNR, Detector, AComponent, Herald, Port, Experiment, CompiledCircuit from perceval.utils import Matrix, BasicState, SVDistribution, BSDistribution, BSCount, BSSamples, StateVector, \ simple_float, NoiseModel, PostSelect from base64 import b64encode import json def b64encoding(obj: bytes) -> str: return b64encode(obj).decode('utf-8') @dispatch(bool, str) def _handle_compress_parameter(compress, type_str) -> bool: return compress @dispatch(list, str) def _handle_compress_parameter(compress, type_str) -> bool: return type_str in compress def _handle_compression(serialized_obj: str, do_compress: bool) -> str: if not do_compress: return serialized_obj serialized_string_compressed = zlib_compress(serialized_obj.encode('utf-8')) # Compress byte to byte serialized_string_compressed_byt2str = b64encoding(serialized_string_compressed) # base64 to string return ZIP_PREFIX + serialized_string_compressed_byt2str @dispatch(AComponent, compress=(list, bool)) def serialize(component: AComponent, compress=None) -> str: if compress is None: compress = True tag = COMPONENT_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression( f"{PCVL_PREFIX}{tag}{SEP}" + b64encoding(serialize_component(component).SerializeToString()), do_compress=compress) @dispatch(ACircuit, compress=(list, bool)) def serialize(circuit: ACircuit, compress=None) -> str: if compress is None: compress = True tag = CIRCUIT_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression( f"{PCVL_PREFIX}{tag}{SEP}" + b64encoding(serialize_circuit(circuit).SerializeToString()), do_compress=compress) @dispatch(Experiment, compress=(list, bool)) def serialize(experiment: Experiment, compress=None) -> str: if compress is None: compress = True tag = EXPERIMENT_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression( f"{PCVL_PREFIX}{tag}{SEP}" + b64encoding(serialize_experiment(experiment).SerializeToString()), do_compress=compress) @dispatch(CompiledCircuit, compress=(list, bool)) def serialize(compiled_circuit: CompiledCircuit, compress=None) -> str: if compress is None: compress = True tag = COMPILED_CIRCUIT_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression( f"{PCVL_PREFIX}{tag}{SEP}" + b64encoding(serialize_compiled_circuit(compiled_circuit).SerializeToString()), do_compress=compress) @dispatch(Herald, compress=(list, bool)) def serialize(herald: Herald, compress=None) -> str: if compress is None: compress = True tag = HERALD_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression( f"{PCVL_PREFIX}{tag}{SEP}" + b64encoding(serialize_herald(herald).SerializeToString()), do_compress=compress) @dispatch(Port, compress=(list, bool)) def serialize(port: Port, compress=None) -> str: if compress is None: compress = True tag = PORT_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression( f"{PCVL_PREFIX}{tag}{SEP}" + b64encoding(serialize_port(port).SerializeToString()), do_compress=compress) @dispatch(Matrix, compress=(list, bool)) def serialize(m: Matrix, compress=None) -> str: if compress is None: compress = False tag = MATRIX_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression(f"{PCVL_PREFIX}{tag}{SEP}" + b64encoding(serialize_matrix(m).SerializeToString()), do_compress=compress) @dispatch(BasicState, compress=(list, bool)) def serialize(obj, compress=None) -> str: if compress is None: compress = False tag = BS_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression(f"{PCVL_PREFIX}{tag}{SEP}" + serialize_state(obj), do_compress=compress) @dispatch(StateVector, compress=(list, bool)) def serialize(sv, compress=None) -> str: if compress is None: compress = False tag = SV_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression(f"{PCVL_PREFIX}{tag}{SEP}" + serialize_statevector(sv), do_compress=compress) @dispatch(SVDistribution, compress=(list, bool)) def serialize(dist: SVDistribution, compress=None) -> str: if compress is None: compress = False tag = SVD_TAG compress = _handle_compress_parameter(compress, tag) serial_svd = f"{PCVL_PREFIX}{tag}{SEP}{{" \ + ";".join(["%s=%s" % (serialize_statevector(k), simple_float(v, nsimplify=False)[1]) for k, v in dist.items()]) \ + "}" return _handle_compression(serial_svd, do_compress=compress) @dispatch(BSDistribution, compress=(list, bool)) def serialize(dist: BSDistribution, compress=None) -> str: if compress is None: compress = True tag = BSD_TAG compress = _handle_compress_parameter(compress, tag) serial_bsd = f"{PCVL_PREFIX}{tag}{SEP}{{" \ + ";".join(["%s=%s" % (serialize_state(k), simple_float(v, nsimplify=False)[1]) for k, v in dist.items()]) \ + "}" return _handle_compression(serial_bsd, do_compress=compress) @dispatch(BSCount, compress=(list, bool)) def serialize(obj, compress=None) -> str: if compress is None: compress = True tag = BSC_TAG compress = _handle_compress_parameter(compress, tag) serial_bsc = f"{PCVL_PREFIX}{tag}{SEP}{{" \ + ";".join(["%s=%s" % (serialize_state(k), str(v)) for k, v in obj.items()]) \ + "}" return _handle_compression(serial_bsc, do_compress=compress) @dispatch(BSSamples, compress=(list, bool)) def serialize(obj, compress=None) -> str: if compress is None: compress = True tag = BSS_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression(f"{PCVL_PREFIX}{tag}{SEP}" + serialize_bssamples(obj), do_compress=compress) @dispatch(NoiseModel, compress=(list, bool)) def serialize(obj, compress=None): if compress is None: compress = False tag = NOISE_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression(f"{PCVL_PREFIX}{tag}{SEP}" + json.dumps(obj.__dict__()), do_compress=compress) @dispatch(PostSelect, compress=(list, bool)) def serialize(ps: PostSelect, compress=None): if compress is None: compress = False tag = POSTSELECT_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression(f"{PCVL_PREFIX}{tag}{SEP}{ps}", do_compress=compress) @dispatch(BSLayeredPPNR, compress=(list, bool)) def serialize(obj: BSLayeredPPNR, compress=None): if compress is None: compress = False tag = BS_LAYERED_DETECTOR_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression(f"{PCVL_PREFIX}{tag}{SEP}" + b64encoding(serialize_bs_layer(obj).SerializeToString()), do_compress=compress) @dispatch(Detector, compress=(list, bool)) def serialize(obj: Detector, compress=None): if compress is None: compress = False tag = DETECTOR_TAG compress = _handle_compress_parameter(compress, tag) return _handle_compression(f"{PCVL_PREFIX}{tag}{SEP}" + b64encoding(serialize_detector(obj).SerializeToString()), do_compress=compress) @dispatch(dict, compress=(list, bool)) def serialize(obj, compress=None) -> dict: r = {} for k, v in obj.items(): r[serialize(k, compress=compress)] = serialize(v, compress=compress) return r @dispatch(list, compress=(list, bool)) def serialize(obj, compress=None) -> list: r = [] for k in obj: r.append(serialize(k, compress=compress)) return r @dispatch(object, compress=(list, bool)) def serialize(obj, compress=None) -> object: return obj def serialize_to_file(obj, filepath: str, compress=None) -> None: serial_repr = serialize(obj, compress=compress) with open(filepath, mode="w") as f: f.write(json.dumps(serial_repr)) ================================================ FILE: perceval/serialization/serialize_binary.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """ Functions which output the binary representation of objects having a protobuf serializer This binary representation loses all knowledge about the type of the input object and have to be deserialized using specialized deserialize functions (e.g. deserialize_circuit) """ from multipledispatch import dispatch from ._circuit_serialization import serialize_circuit from ._matrix_serialization import serialize_matrix from perceval.components.linear_circuit import ACircuit from perceval.utils.matrix import Matrix @dispatch(ACircuit) def serialize_binary(circuit: ACircuit): return serialize_circuit(circuit).SerializeToString() @dispatch(Matrix) def serialize_binary(matrix: Matrix): return serialize_matrix(matrix).SerializeToString() ================================================ FILE: perceval/simulators/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .delay_simulator import DelaySimulator from .loss_simulator import LossSimulator from .polarization_simulator import PolarizationSimulator from .simulator import Simulator from .simulator_factory import SimulatorFactory from .stepper import Stepper from .noisy_sampling_simulator import NoisySamplingSimulator from .feed_forward_simulator import FFSimulator ================================================ FILE: perceval/simulators/_simulate_detectors.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from typing import Callable from perceval.runtime import cancel_requested from perceval.components.detector import IDetector, DetectionType, get_detection_type from perceval.utils import BSDistribution, FockState def heralds_compatible_threshold(s: FockState, heralds: dict[int, int]) -> bool: """ :param s: The state to check :param heralds: The heralds {mode: count} to check compatibility with :return: True if the state will match the heralds after being thresholded, False otherwise. """ for m, v in heralds.items(): if v and not s[m]: # Note: this case should not happen if an "at least 1" condition was applied on previous step return False if v == 0 and s[m]: return False return True def compute_distributions(s: FockState, detectors: list[IDetector], heralds: dict[int, int]) -> list[BSDistribution]: """ :param s: The state to compute distributions for given the detectors. :param detectors: The detectors to apply to get the distributions :param heralds: The heralds that the final state needs to satisfy :return: The list of computed BSDistributions that need to be merged afterward. For modes where there are heralds, either: - the distribution is a non-normalized, single-state distribution (only the useful state is kept) - the returned list is empty (no useful state is reachable) """ distributions = [] for m, (photons_in_mode, detector) in enumerate(zip(s, detectors)): if detector is not None: d = detector.detect(photons_in_mode) if isinstance(d, FockState): d = BSDistribution(d) if m in heralds: v = heralds[m] state = FockState([v]) p = d[state] if not p: return [] # Note: if heralds have been correctly applied before, this case can't appear d = BSDistribution({state: p}) distributions.append(d) elif m not in heralds or heralds[m] == photons_in_mode: distributions.append(BSDistribution(FockState([photons_in_mode]))) else: return [] return distributions def simulate_detectors(dist: BSDistribution, detectors: list[IDetector], min_photons: int = 0, prob_threshold: float = 0, heralds: dict[int, int] = {}, progress_callback: Callable = None) -> tuple[BSDistribution, float]: """ Simulates the effect of imperfect detectors on a theoretical distribution. :param dist: A theoretical distribution of detections, as would Photon Number Resolving (PNR) detectors detect. :param detectors: A List of detectors :param min_photons: Minimum detected photons filter value (when None, does not apply this physical filter) :param prob_threshold: Filter states that have a probability below this threshold :param heralds: A dictionary {mode: expected} that will be used for logical selection. Beware the performance can only be considered global (and no longer physical) if not empty :param progress_callback: A function with the signature `func(progress: float, message: str)` :return: A tuple containing the output distribution where detectors were simulated, and a physical performance score """ assert len(detectors) == dist.m, "Mismatch between the number of detectors and the number of modes!" detection = get_detection_type(detectors) if not dist or detection == DetectionType.PNR: return dist, 1 phys_perf = 0 result = BSDistribution() lbsd = len(dist) if detection == DetectionType.Threshold: for idx, (s, p) in enumerate(dist.items()): if not heralds_compatible_threshold(s, heralds): continue s = s.threshold_detection() if s.n >= min_photons: phys_perf += p result[s] += p if progress_callback and idx % 250000 == 0: # Every 250000 states progress = (idx + 1) / lbsd exec_request = progress_callback(progress, "simulate detectors") if cancel_requested(exec_request): raise RuntimeError("Cancel requested") if len(result): result.normalize() return result, phys_perf for idx, (s, p) in enumerate(dist.items()): if progress_callback and idx % 100000 == 0: # Every 100000 states progress = (idx + 1) / lbsd exec_request = progress_callback(progress, "simulate detectors") if cancel_requested(exec_request): raise RuntimeError("Cancel requested") distributions = compute_distributions(s, detectors, heralds) if not distributions: continue state_dist = BSDistribution.list_tensor_product(distributions, prob_threshold=max(prob_threshold, prob_threshold / (10 * p) if p > 0 else prob_threshold)) # "magic factor" 10 like in the simulator for s_out, p_out in state_dist.items(): if s_out.n >= min_photons: prob = p * p_out phys_perf += prob result.add(s_out, prob) if len(result): result.normalize() return result, phys_perf def simulate_detectors_sample(sample: FockState, detectors: list[IDetector], detection: DetectionType = None, heralds: dict[int, int] = {}, ) -> FockState | None: """ Simulate detectors effect on one output sample. If multiple possible outcome exist, one is randomly chosen :param sample: The sample to simulate detectors on :param detectors: A list of detectors (with the same length as the sample) :param detection: An optional detection type. Can be recomputed from the detectors list, but it's faster to compute it once and pass it for a list a samples to process :param heralds: A dictionary {mode: expected} that will be used for logical selection. If the returned state doesn't fulfil this, this function will early return None. :return: The output sample where the detector imperfection were applied, or None if the state is logically rejected for the heralds """ if detection is None: detection = get_detection_type(detectors) if detection == DetectionType.PNR: return sample if detection == DetectionType.Threshold: if heralds_compatible_threshold(sample, heralds): return sample.threshold_detection() return None out_state = FockState() for m, (photons_in_mode, detector) in enumerate(zip(sample, detectors)): state_distrib = detector.detect(photons_in_mode) if detector is not None else FockState([photons_in_mode]) if isinstance(state_distrib, BSDistribution): state_distrib = state_distrib.sample(1, non_null=False)[0] if m in heralds and state_distrib[0] != heralds[m]: return None out_state *= state_distrib return out_state ================================================ FILE: perceval/simulators/_simulator_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from collections import defaultdict from math import sqrt from multipledispatch import dispatch from typing import Iterable from perceval.utils import FockState, NoisyFockState, AnnotatedFockState, BSDistribution, StateVector, Annotation, SVDistribution from perceval.components import Circuit def _to_bsd(sv: StateVector) -> BSDistribution: res = BSDistribution() for state, pa in sv.unnormalized_iterator(): if not isinstance(state, FockState): state = state.clear_annotations() res.add(state, abs(pa) ** 2) return res @dispatch(StateVector, Annotation) def _inject_annotation(sv: StateVector, annotation: Annotation) -> StateVector: if len(annotation): res_sv = StateVector() for s, pa in sv.unnormalized_iterator(): s = s.inject_annotation(annotation) res_sv += pa * s return res_sv return sv @dispatch(StateVector, int) def _inject_annotation(sv: StateVector, annotation: int) -> StateVector: res_sv = StateVector() for s, pa in sv.unnormalized_iterator(): s = s.inject_annotation(annotation) res_sv += pa * s return res_sv def _merge_sv(sv1: StateVector, sv2: StateVector, prob_threshold: float = 0) -> StateVector: if not sv1: return sv2 pa_threshold = sqrt(prob_threshold) res = StateVector() for s1, pa1 in sv1.unnormalized_iterator(): for s2, pa2 in sv2.unnormalized_iterator(): pa = pa1 * pa2 if abs(pa) > pa_threshold: res += s1.merge(s2)*pa return res @dispatch(FockState) def _annot_state_mapping(bs_with_annots: FockState): return {Annotation(): bs_with_annots} @dispatch(NoisyFockState) def _annot_state_mapping(bs_with_annots: NoisyFockState): return bs_with_annots.split_state() @dispatch(AnnotatedFockState) def _annot_state_mapping(bs_with_annots: AnnotatedFockState): raise ValueError("AnnotatedFockState can't be used to separate states. " "If needed, you can convert it to a NoisyFockState using a string representation like |{0}, {1}>.") @dispatch(FockState) def _separate_state(bs_with_annots: FockState): return [bs_with_annots] @dispatch(NoisyFockState) def _separate_state(bs_with_annots: NoisyFockState): return bs_with_annots.separate_state() @dispatch(AnnotatedFockState) def _separate_state(bs_with_annots: AnnotatedFockState): raise ValueError("AnnotatedFockState can't be used to separate states. " "If needed, you can convert it to a NoisyFockState using a string representation like |{0}, {1}>.") def _retrieve_mode_count(component_list: list) -> int: return max([m for r in component_list for m in r[0]]) + 1 def _unitary_components_to_circuit(component_list: list, m: int = 0): if not m: m = _retrieve_mode_count(component_list) circuit = Circuit(m) for r, c in component_list: circuit.add(r, c) return circuit def _split_by_photon_and_tag_count(sv: StateVector) -> SVDistribution: """ Split a state vector into a SVDistribution such that each key of the SVD corresponds to one photon count and one noise tag count """ counter = defaultdict(lambda: [StateVector(), 0]) # State and prob for state, pa in sv: if isinstance(state, NoisyFockState): split = state.split_state() key = tuple(split[tag].n if tag in split else 0 for tag in range(max(split.keys()) + 1)) else: key = state.n counter[key][0] += pa * state counter[key][1] += abs(pa) ** 2 res = SVDistribution() for (state, prob) in counter.values(): state.normalize() res[state] = prob return res def _list_merge(distributions: list[Iterable[tuple[FockState, float]]], prob_threshold: float = 0) -> dict[FockState, float]: # Note: the iterable must reset each time we call it if len(distributions) == 0: return dict() res = defaultdict(float) def _inner_tensor_product(dist: list[Iterable[tuple[FockState, float]]], current_state: FockState, current_prob: float): if len(dist) == 0: res[current_state] += current_prob return for bs, p in dist[0]: prob = current_prob * p if prob < prob_threshold: continue _inner_tensor_product(dist[1:], current_state.merge(bs), prob) for bs, p in distributions[0]: if p < prob_threshold: continue _inner_tensor_product(distributions[1:], bs, p) return res ================================================ FILE: perceval/simulators/delay_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from copy import copy from .simulator_interface import ASimulatorDecorator from ._simulator_utils import _retrieve_mode_count, _unitary_components_to_circuit from perceval.components import ACircuit, PERM, TD, IDetector from perceval.utils import FockState, BSDistribution, StateVector, global_params, PostSelect from enum import Enum class _CType(Enum): UNITARY = 0 DELAY = 1 OTHER = 2 def _count_total_delay(component_list: list) -> int: return int(sum([c.get_variables()["t"] if isinstance(c, TD) else 0 for _, c in component_list])) def _compute_depth(component_list: list, mode_count: int) -> int: depth = 1 count_per_mode = [[0, {i}] for i in range(mode_count)] for r, c in component_list: if isinstance(c, TD): t = c.get_variables()["t"] assert not isinstance(t, str), "Delay parameter %s not set" % t if t != int(t): raise NotImplementedError("Only round time delays are supported") t = int(t) r = r[0] if not len(count_per_mode[r][1]) == 1: cur_count = max(count_per_mode[i][0] for i in count_per_mode[r][1]) depth += cur_count for mode in count_per_mode[r][1]: count_per_mode[mode][1] = {mode} for i in range(mode_count): count_per_mode[i][0] = max(0, count_per_mode[i][0] - cur_count) count_per_mode[r][0] += t elif len(r) > 1: set_r = set(r) for mode in r: count_per_mode[mode][1] |= set_r depth += max(count_per_mode[i][0] for i in range(mode_count)) return depth class DelaySimulator(ASimulatorDecorator): _can_transmit_selection = True def __init__(self, simulator): super().__init__(simulator) self._original_m: int = 0 self._expanded_m: int = 0 self._depth: int = 0 def _prepare_input(self, input_state): if isinstance(input_state, tuple): expanded_input = input_state[1] ** self._depth return input_state[0], expanded_input * FockState([0] * (self._expanded_m - self._depth * self._original_m)) expanded_input = input_state ** self._depth return expanded_input * FockState([0] * (self._expanded_m - self._depth * self._original_m)) def _prepare_circuit(self, circuit, m=None): if m is None: self._original_m = _retrieve_mode_count(circuit) else: assert m >= _retrieve_mode_count(circuit), "Given circuit size is too small for the components" self._original_m = m expanded_circuit, expanded_mode_count = self._expand_td(circuit) self._expanded_m = expanded_mode_count return expanded_circuit def _prepare_detectors_impl(self, detectors: list[IDetector]): return [None] * ((self._depth - 1) * self._original_m) \ + detectors \ + [None] * (self._expanded_m - self._depth * self._original_m) def _mode_range(self) -> tuple[int, int]: return (self._depth - 1) * self._original_m, self._depth * self._original_m def _postprocess_bsd_impl(self, results): output = BSDistribution() m = self._mode_range() for out_state, output_prob in results.items(): if output_prob > global_params['min_p']: reduced_out_state = out_state[m[0]:m[1]] output[reduced_out_state] += output_prob return output def _postprocess_sv_impl(self, results: StateVector) -> StateVector: output = StateVector() m = self._mode_range() for out_state, probampli in results: if abs(probampli) > global_params['min_complex_component']: reduced_out_state = out_state[m[0]:m[1]] output += probampli * reduced_out_state return output def _transmit_heralds_postselect(self): heralds = {} if self._heralds: heralds = {m + self._mode_range()[0]: v for m, v in self._heralds.items()} postselect = PostSelect() if self._postselect.has_condition: postselect = copy(self._postselect) postselect.shift_modes(self._mode_range()[0]) self._simulator.set_selection(postselect=postselect, heralds=heralds) def _expand_td(self, component_list: list): mode_count = self._original_m expanded = [] current_chunk = [] can_output_circuit = True for r, c in component_list: if isinstance(c, ACircuit): # case unitary component current_chunk.append([r, c]) elif not isinstance(c, TD): # case other non-unitary component expanded.append([_CType.UNITARY, current_chunk.copy()]) current_chunk = [] expanded.append([_CType.OTHER, [[r, c]]]) can_output_circuit = False else: # case time delay t = float(c.param("t")) if not t.is_integer(): raise ValueError(f"'t' parameter must be an integer, got {t}") t = int(t) r0 = r[0] if r0 != mode_count - 1: # Add a fake permutation to put the TD at the last mode r = list(range(r0, mode_count)) perm_list = [mode_count - r0 - 1] + list(range(1, mode_count - r0 - 1)) + [0] current_chunk.append([r, PERM(perm_list)]) for _ in range(t): expanded.append([_CType.UNITARY, current_chunk.copy()]) expanded.append([_CType.DELAY, []]) current_chunk = [] if r0 != mode_count - 1: # Nullify the fake permutation current_chunk.append([r, PERM(perm_list)]) expanded.append([_CType.UNITARY, current_chunk.copy()]) self._depth = _compute_depth(component_list, mode_count) total_delay = _count_total_delay(component_list) new_m = self._depth * mode_count + total_delay new_circ = [] for d in range(self._depth): i_td = 0 for i, type_and_cur_U in enumerate(expanded): ctype, current = type_and_cur_U if ctype != _CType.DELAY: for r, c in current: new_circ.append((r[0] + d * mode_count, c)) # Then we permute to mimic TD else: r0 = (d + 1) * mode_count - 1 perm_list = [new_m - i_td - r0 - 1] + list(range(1, new_m - i_td - r0 - 1)) + [0] i_td += 1 new_circ.append((r0, PERM(perm_list))) if can_output_circuit: new_circ = _unitary_components_to_circuit(new_circ, new_m) return new_circ, new_m ================================================ FILE: perceval/simulators/feed_forward_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import copy from typing import Any from perceval.components import Processor, AComponent, Barrier, PERM, IDetector, Herald, PortLocation, Source, Experiment from perceval.utils import (NoiseModel, BasicState, FockState, BSDistribution, SVDistribution, StateVector, partial_progress_callable, get_logger, deprecated) from perceval.utils.logging import channel from perceval.components.feed_forward_configurator import AFFConfigurator from perceval.backends import AStrongSimulationBackend, IFFBackend from .simulator_interface import ISimulator class FFSimulator(ISimulator): def __init__(self, backend: AStrongSimulationBackend): super().__init__() self._precision = None self._components = None self._backend = backend self._noise_model = None def compute_physical_logical_perf(self, value: bool): # TODO: decide what happens to this if value: get_logger().warn("Only the global performance can be computed for a feed-forward simulator.") def set_circuit(self, circuit: Processor | Experiment | list[tuple[tuple, AComponent]], m=None): if isinstance(circuit, (Processor, Experiment)): self._components = circuit.components min_detected_photons = circuit._min_detected_photons_filter post_select = circuit.post_select_fn heralds = circuit.heralds self.set_selection(min_detected_photons, post_select, heralds) else: self._components = circuit def set_noise(self, nm: NoiseModel): self._noise_model = nm @deprecated("Version 1.1 - Source is no longer used") def set_source(self, source: Source): pass def _probs_svd(self, input_state: SVDistribution | tuple[Source, BasicState], detectors: list[IDetector] = None, progress_callback: callable = None) -> tuple[BSDistribution, float]: # 1: Find all the FFConfigurators that can be simulated without measuring more modes considered_config, measured_modes, unsafe_modes = self._find_next_simulation_layer() # 2: Launch a simulation with the default circuits components = self._components.copy() default_circuits = [] for i, config in considered_config: r = self._components[i][0] circ_r = config.config_modes(r) default_circuits.append(config.default_circuit) # Use only the first mode as circuits can have different sizes components[i] = (circ_r[0], config.default_circuit) # We can't reject any state at this moment since we need all possible measured states # Except for heralds on safe modes (i.e. not subject to feed-forward anywhere) new_heralds = {r: v for r, v in self._heralds.items() if r not in unsafe_modes} if self._heralds is not None else None # TODO: in theory, if we can split the Postselect keeping only the safe modes, # it can be even faster by removing more impossible measures (thus not simulating them) # Estimation of possible measures: n for each measured mode if isinstance(input_state, tuple): n = input_state[1].n m = input_state[1].m if self._noise_model is not None and self._noise_model.g2 > 0: n *= 2 else: n = input_state.n_max m = input_state.m intermediate_progress = 1 / n ** len(measured_modes) prog_cb = partial_progress_callable(progress_callback, max_val=intermediate_progress) default_res = self._simulate(input_state, components, m, detectors, prog_cb, new_heralds=new_heralds) default_norm_factor = default_res["global_perf"] default_res = default_res["results"] # 3: deduce all measurable states and launch one simulation for each of them res = BSDistribution() simulated_measures = set() global_perf = 0 # = P(logic_filter and n >= photon_filter) prog_cb = partial_progress_callable(progress_callback, min_val=intermediate_progress) for j, (state, prob) in enumerate(default_res.items()): sub_circuits = [config.configure(state[slice(self._components[i][0][0], self._components[i][0][-1] + 1)]) for i, config in considered_config] if all(c1 == default_circuit for c1, default_circuit in zip(sub_circuits, default_circuits)): # Default case, no need for further computation # There is a problem here: if the same circuit is instanced twice, the == operator returns False # This can be a problem if we copy the default circuit or instantiate a new one if self._post_process_state(state): prob *= default_norm_factor res[self._remove_heralds(state[:m])] = prob global_perf += prob if prog_cb is not None: prog_cb((j + 1) / len(default_res), "Computing feed-forwarded circuit") continue measured_state = tuple(state[i] for i in measured_modes) if measured_state not in simulated_measures: for sub_i, (i, config) in enumerate(considered_config): r = self._components[i][0] # Does not use the list of modes as circuits can have different sizes components[i] = (config.config_modes(r)[0], sub_circuits[sub_i]) new_heralds = {i: state[i] for i in measured_modes if i not in self._heralds} new_prog_cb = partial_progress_callable(prog_cb, j / len(default_res), (j + 1) / len(default_res)) sub_res = self._simulate(input_state, components, m, detectors, new_prog_cb, filter_states=True, new_heralds=new_heralds) norm_factor = sub_res["global_perf"] # The remaining states are only the ones with n >= filter and mask global_perf += norm_factor for st, p in sub_res["results"].items(): # No need for post_process here: results are already post-processed by the sub simulator res[self._remove_heralds(st[:m])] = p * norm_factor simulated_measures.add(measured_state) if len(res): res.normalize() return res, global_perf def _find_next_simulation_layer(self) -> tuple[list[tuple[int, AFFConfigurator]], list[int], set[int]]: """ :return: The list containing the tuples with the index in the component list of the configuration independent FFConfigurators and their instances, the list of the associated measured modes, and the list of modes that are touched at anytime by feed-forward configurators (including after the layer) """ # We can add a configurator as long as the measured mode don't come from a configurable circuit feed_forwarded_modes: set[int] = set() measured_modes = set() res = [] lock_res = False for i, (r, c) in enumerate(self._components): if isinstance(c, AFFConfigurator): if not lock_res and any(r0 in feed_forwarded_modes for r0 in r): lock_res = True feed_forwarded_modes.update(c.config_modes(r)) if not lock_res: res.append((i, c)) measured_modes.update(r) elif isinstance(c, Barrier): continue elif isinstance(c, PERM): to_remove = [] to_add = [] perm_vector = c.perm_vector for new_mode in r: if new_mode in feed_forwarded_modes: to_remove.append(new_mode) to_add.append(perm_vector[new_mode - r[0]] + r[0]) feed_forwarded_modes.difference_update(to_remove) feed_forwarded_modes.update(to_add) elif any(new_mode in feed_forwarded_modes for new_mode in r): feed_forwarded_modes.update(r) return res, list(measured_modes), feed_forwarded_modes def _get_sim_params(self, input_state: SVDistribution | tuple[Source, BasicState], components: list[tuple[tuple, AComponent | Processor]], m: int, detectors: list[IDetector] = None, filter_states: bool = False, new_heralds: dict[int, int] = None): """Initialize a new simulator with the given components and heralds. Heralds that are already in this simulator are still considered. :param input_state: The input state used for the simulation :param components: A list of components that will be added in the simulation. Can themselves be processors. :param filter_states: Whether the states should be filtered in the sub-simulation. :param new_heralds: The list of heralds that should be added, containing the position and the value :return: A configured simulator to run, the input state to give it, and the detectors to use """ if detectors is None: detectors = m * [None] proc = Processor(self._backend, m) if self._noise_model is not None: proc.noise = self._noise_model for r, c in components: proc.add(r, c) # Now the Processor has only the heralds that were possibly added by adding Processors as input, all at the end if isinstance(input_state, SVDistribution): heralded_dist = proc.generate_noisy_heralds() if len(heralded_dist): input_state = input_state * heralded_dist # Must not change the original object else: proc.with_input(input_state[1]) input_state = (input_state[0], proc.input_state) sum_new_heralds = 0 if new_heralds is not None: for r, v in new_heralds.items(): proc.add_port(r, Herald(v), PortLocation.OUTPUT) sum_new_heralds += v if filter_states: if self._heralds is not None: for r, v in self._heralds.items(): proc.add_port(r, Herald(v), PortLocation.OUTPUT) if self._postselect.has_condition: if proc.post_select_fn is not None: postselect = copy.copy(self._postselect) postselect.merge(proc.post_select_fn) else: postselect = self._postselect proc.set_postselection(postselect) # We need to retrieve the new heralds as they are actually counting user photons proc.min_detected_photons_filter(self._min_detected_photons_filter - sum_new_heralds) else: # In that case, the new heralds are user-defined heralds that we can safely simulate. # We can't filter more due to potential losses that would depend on the FF parts of the circuit proc.min_detected_photons_filter(0) from .simulator_factory import SimulatorFactory # Avoids a circular import sim = SimulatorFactory.build(proc) if self._precision is not None: sim.set_precision(self._precision) sim.set_silent(True) return sim, input_state, detectors + proc.detectors[m:] def _simulate(self, input_state: SVDistribution | tuple[Source, BasicState], components: list[tuple[tuple, AComponent | Processor]], m: int, detectors: list[IDetector], prog_cb=None, filter_states: bool = False, new_heralds: dict[int, int] = None) \ -> dict[str, Any]: sim, input_state, detectors = self._get_sim_params(input_state, components, m, detectors, filter_states, new_heralds) return sim.probs_svd(input_state, detectors, prog_cb) def _post_process_state(self, bs: BasicState) -> bool: """Returns True if the state checks all requirements of the simulator""" if bs.n < self.min_detected_photons_filter: return False for m, v in self._heralds.items(): if bs[m] != v: return False return self._postselect(bs) def _remove_heralds(self, state: BasicState) -> BasicState: if not self._keep_heralds: return state.remove_modes(list(self._heralds.keys())) return state def probs(self, input_state: BasicState) -> BSDistribution: """ Compute the probability distribution from a BasicState input :param input_state: A basic state describing the input to simulate :return: A BSDistribution """ if isinstance(self._backend, IFFBackend) and self._backend.can_simulate_feed_forward(self._components, input_state): m = input_state.m get_logger().info("Perform a direct feed-forward simulation", channel.general) sim = self._get_sim_params(SVDistribution(input_state), [], m, filter_states = True)[0] sim.keep_heralds(self._keep_heralds) self._backend.set_feed_forward(self._components, m) return sim.probs(input_state) return self._probs_svd(SVDistribution(input_state))[0] def probs_svd(self, input_dist: SVDistribution, detectors: list[IDetector] = None, progress_callback: callable = None): """ Compute the probability distribution from a SVDistribution input and as well as performance scores :param input_dist: A state vector distribution describing the input to simulate :param detectors: An optional list of detectors :param progress_callback: A function with the signature `func(progress: float, message: str)` :return: A dictionary of the form { "results": BSDistribution, "global_perf": float } * results is the post-selected output state distribution * global_perf is the probability that a state is post-selected """ if isinstance(self._backend, IFFBackend) and self._backend.can_simulate_feed_forward(self._components, input_dist, detectors): if isinstance(input_dist, tuple): m = input_dist[1].m else: m = input_dist.m get_logger().info("Perform a direct feed-forward simulation", channel.general) sim, input_dist, detectors = self._get_sim_params(input_dist, [], m, detectors, filter_states=True) sim.compute_physical_logical_perf(self._compute_physical_logical_perf) sim.keep_heralds(self._keep_heralds) self._backend.set_feed_forward(self._components, m) return sim.probs_svd(input_dist, detectors, progress_callback) res = self._probs_svd(input_dist, detectors, progress_callback) return {'results': res[0], 'global_perf': res[1]} def evolve(self, input_state: FockState | StateVector) -> StateVector: if not isinstance(self._backend, IFFBackend) or not self._backend.can_simulate_feed_forward(self._components, input_state): raise RuntimeError("Cannot perform state evolution with feed-forward") m = input_state.m sim = self._get_sim_params(SVDistribution(input_state), [], m, filter_states = True)[0] sim.keep_heralds(self._keep_heralds) self._backend.set_feed_forward(self._components, m) return sim.evolve(input_state) def set_precision(self, precision: float): self._precision = precision ================================================ FILE: perceval/simulators/loss_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .simulator_interface import ASimulatorDecorator from ._simulator_utils import _retrieve_mode_count, _unitary_components_to_circuit from perceval.components import ACircuit, LC, PERM, BS, IDetector from perceval.utils import FockState, BSDistribution, StateVector class LossSimulator(ASimulatorDecorator): _can_transmit_selection = True def _prepare_input(self, input_state): if isinstance(input_state, tuple): return input_state[0], input_state[1] * FockState([0] * (self._expanded_m - self._original_m)) return input_state * FockState([0] * (self._expanded_m - self._original_m)) def _prepare_circuit(self, circuit, m = None): if m is None: self._original_m = _retrieve_mode_count(circuit) else: assert m >= _retrieve_mode_count(circuit), "Given circuit size is too small for the components" self._original_m = m expanded_circuit = self._simulate_losses_with_beam_splitters(circuit) return expanded_circuit def _prepare_detectors_impl(self, detectors: list[IDetector]): return detectors + [None] * (self._expanded_m - self._original_m) def _postprocess_bsd_impl(self, results: BSDistribution) -> BSDistribution: output = BSDistribution() for out_state, output_prob in results.items(): reduced_out_state = out_state[0:self._original_m] output[reduced_out_state] += output_prob return output def _postprocess_sv_impl(self, sv: StateVector) -> StateVector: output = StateVector() for out_state, probampli in sv: reduced_out_state = out_state[0:self._original_m] output += probampli*reduced_out_state return output def _transmit_heralds_postselect(self): self._simulator.set_selection(postselect=self._postselect, heralds=self._heralds) def _simulate_losses_with_beam_splitters(self, components: list) -> ACircuit: output = [] can_output_circuit = True next_free_mode = self._original_m for r, c in components: if isinstance(c, ACircuit): # case unitary component output.append((r, c)) elif not isinstance(c, LC): # case other non-unitary component output.append((r, c)) can_output_circuit = False else: # case loss channel if r[0] != next_free_mode - 1: r_ip = tuple(range(r[0]+1, next_free_mode+1)) in_perm = [next_free_mode-r[0]-1] + [m for m in range(1, next_free_mode-r[0]-1)] + [0] output.append((r_ip, PERM(in_perm))) loss = float(c.param("loss")) bs = BS.H(BS.r_to_theta(1 - loss)) r_bs = (r[0], r[0]+1) output.append((r_bs, bs)) if r[0] != next_free_mode - 1: out_perm = PERM(in_perm) out_perm.inverse(h=True) output.append((r_ip, out_perm)) next_free_mode += 1 self._expanded_m = next_free_mode if can_output_circuit: output = _unitary_components_to_circuit(output, self._expanded_m) return output ================================================ FILE: perceval/simulators/noisy_sampling_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import time import sys from collections import defaultdict from typing import Callable from perceval.backends import ASamplingBackend from perceval.components import ACircuit, IDetector, get_detection_type, DetectionType, check_heralds_detectors, Source from perceval.utils import BasicState, FockState, StateVector, NoisyFockState, BSCount, BSSamples, SVDistribution, PostSelect, \ samples_to_sample_count from perceval.utils.logging import get_logger, channel from perceval.runtime import cancel_requested from ._simulate_detectors import simulate_detectors_sample class SamplesProvider: def __init__(self, sampling_backend: ASamplingBackend): self._backend = sampling_backend self._pools: dict[BasicState, list[BasicState] | BSSamples] = defaultdict(list) self._weights = BSCount() self._sample_coeff = 1.1 self._min_samples = 100 # to be sampled at once self._max_samples = 2000 # to be sampled at once. Needs to be at least 10 * _min_samples to be coherent self.sleep_between_batches = 0.2 def prepare(self, progress_callback: callable = None): """ Compute a first batch of outputs for all the inputs whose weight has been estimated :param progress_callback: Optional callback function with signature progress_callback(progress: float, message: str) """ get_logger().debug(f"Prepare {len(self._weights)} pools of a total of {self._weights.total()} samples", channel.general) for input_state, count in self._weights.items(): count = min(count, self._max_samples) if input_state.n == 0: self._pools[input_state] = [input_state] * count else: self._backend.set_input_state(input_state) self._pools[input_state] = self._backend.samples(count) self._weights[input_state] = math.ceil(0.1 * self._weights[input_state]) if progress_callback: cancel_request = progress_callback(0, 'prepare sampling') time.sleep(self.sleep_between_batches) # else callback method doesn't have time to be called if cancel_request is not None and cancel_request.get('cancel_requested', False): break def estimate_weights_from_distribution(self, noisy_input: SVDistribution, n_samples: int): """ Decide how much of each input we will generate when the pool becomes empty based on the probability of seeing such an input state and the total number of samples. :param noisy_input: The trimmed input distribution after selecting the states having enough photons :param n_samples: The total number of samples to generate """ if n_samples: for noisy_sv, prob in noisy_input.items(): for noisy_s in noisy_sv.keys(): ns = min(math.ceil(prob * n_samples), self._max_samples) bs_list = [noisy_s] if isinstance(noisy_s, FockState) else noisy_s.separate_state() for bs in bs_list: if self._weights[bs] + ns < self._max_samples: self._weights.add(bs, ns) else: self._weights.add(bs, self._max_samples - self._weights[bs]) def estimate_weights_from_source(self, sample_generator: Callable[[int], list[BSSamples]], n_samples: int) -> list[BSSamples]: """ Decide how much of each input we will generate when the pool becomes empty based on a batch of samples generated from the source and the total number of samples. """ # Generates a batch from the source to estimate the weights. These will also be the first simulated samples input_samples = sample_generator(n_samples) for sample in input_samples: for state in sample: if self._weights[state] < self._max_samples: self._weights.add(state, 1) return input_samples def _compute_samples(self, fock_state: FockState): if fock_state not in self._weights: self._weights[fock_state] = self._min_samples n_samples = self._weights[fock_state] n_samples = min(n_samples, self._max_samples) get_logger().debug(f"Simulate {n_samples} more {fock_state.n}-photon samples", channel.general) self._backend.set_input_state(fock_state) self._pools[fock_state] += self._backend.samples(n_samples) self._weights[fock_state] = min(max(int(self._weights[fock_state] * self._sample_coeff), 16), self._max_samples) def sample_from(self, input_state: FockState) -> BasicState: """Pop an output from the pool of outputs for the given input state. If none is available, computes a batch of outputs based on the associated weight.""" if not len(self._pools[input_state]): self._compute_samples(input_state) return self._pools[input_state].pop() class NoisySamplingSimulator: """ Simulates a sampling, using a perfect sampling algorithm. It is used to take advantage of a parallel sampling algorithm, by computing multiple output states at once, while taking noise and post-processing (heralds, post-selection, detector characteristics) into account :param sampling_backend: Instance of a sampling-capable back-end """ def __init__(self, sampling_backend: ASamplingBackend): self._backend = sampling_backend self._min_detected_photons_filter = 0 self._postselect: PostSelect = PostSelect() self._heralds: dict = {} self._keep_heralds = True self.sleep_between_batches = 0.2 # sleep duration (in s) between two batches of samples self._detectors = None self._compute_physical_logical_perf = False @property def min_detected_photons_filter(self): return self._min_detected_photons_filter + sum(self._heralds.values()) def set_detectors(self, detector_list: list[IDetector]): """ :param detector_list: A list of detectors to simulate """ self._detectors = detector_list def keep_heralds(self, value: bool): """ Tells the simulator to keep or discard ancillary modes in output states :param value: True to keep ancillaries/heralded modes, False to discard them (default is keep). """ self._keep_heralds = value def compute_physical_logical_perf(self, value: bool): """ Tells the simulator to compute or not the physical and logical performances when possible :param value: True to compute the physical and logical performances, False otherwise. """ self._compute_physical_logical_perf = value def set_selection(self, min_detected_photons_filter: int = None, postselect: PostSelect = None, heralds: dict = None): """Set multiple selection filters at once to remove unwanted states from computed output distribution :param min_detected_photons_filter: minimum number of detected photons in the output distribution :param postselect: a post-selection function :param heralds: expected detections (heralds). Only corresponding states will be selected, others are filtered out. Mapping of heralds. For instance `{5: 0, 6: 1}` means 0 photon is expected on mode 5 and 1 on mode 6. """ if min_detected_photons_filter is not None: self._min_detected_photons_filter = min_detected_photons_filter if postselect is not None: self._postselect = postselect if heralds is not None: self._heralds = heralds def _state_selected(self, state: BasicState) -> bool: """ Computes if the state is selected given heralds and post selection function """ for m, v in self._heralds.items(): if state[m] != v: return False if self._postselect is not None: return self._postselect(state) return True def set_circuit(self, circuit: ACircuit): """ Set the circuit to simulate the sampling on :param circuit: A unitary circuit """ self._backend.set_circuit(circuit) def set_min_detected_photons_filter(self, value: int): """ Set the physical detection filter. Any output state with less than this threshold gets discarded. :param value: Minimal photon count in output states of interest. """ self._min_detected_photons_filter = value def _perfect_sampling_no_selection( self, input_state: BasicState, n_samples: int, progress_callback: callable = None) -> dict: self._backend.set_input_state(input_state) samples_acquired = 0 results = BSSamples() while samples_acquired < n_samples: loop_sample_count = min(1000, n_samples - samples_acquired) results += self._backend.samples(loop_sample_count) samples_acquired += loop_sample_count if progress_callback: cancel_request = progress_callback(samples_acquired / n_samples, 'sampling') time.sleep(self.sleep_between_batches) # else callback method doesn't have time to be called if cancel_request is not None and cancel_request.get('cancel_requested', False): break return self.format_results(results, 1, 1) def _noisy_sampling( self, sample_generator: Callable[[int], list[list[BasicState]]], provider: SamplesProvider, max_samples: int, max_shots: int, detection_type: DetectionType, first_batch: list[BSSamples], progress_callback: callable = None) -> dict: output = BSSamples() idx = 0 not_selected = 0 selected_inputs = first_batch not_selected_physical = 0 shots = 0 batch_size = min(max_samples, max_shots) if max_shots is not None else max_samples while len(output) < max_samples and (max_shots is None or shots < max_shots): # Progress handling if progress_callback: exec_request = progress_callback(len(output) / max_samples, "sampling") if cancel_requested(exec_request): break if idx == len(selected_inputs): # Generate new inputs idx = 0 nb_gen = min(batch_size, max_samples - len(output)) if max_shots is not None: nb_gen = min(nb_gen, max_shots - shots) selected_inputs = sample_generator(nb_gen) selected_bs = selected_inputs[idx] # should be a FockState / FockState list idx += 1 # Sampling if len(selected_bs) > 1: # In case of annotations, input must be separately sampled, then recombined sampled_components = [] for bs in selected_bs: sampled_components.append(provider.sample_from(bs)) sampled_state = sampled_components.pop() for component in sampled_components: sampled_state = sampled_state.merge(component) else: sampled_state = provider.sample_from(selected_bs[0]) if self._detectors: sampled_state = simulate_detectors_sample(sampled_state, self._detectors, detection_type, self._heralds if not self._compute_physical_logical_perf else {}) # Post-processing shots += 1 if sampled_state is None: not_selected += 1 continue if sampled_state.n < self.min_detected_photons_filter: not_selected_physical += 1 continue if self._state_selected(sampled_state): if self._heralds and not self._keep_heralds: # Remove ancillary modes sampled_state = sampled_state.remove_modes(list(self._heralds.keys())) output.append(sampled_state) else: not_selected += 1 # Performance estimate selected = len(output) logical_perf = 0 physical_perf = 0 if selected > 0: physical_perf = (selected + not_selected) / (selected + not_selected + not_selected_physical) logical_perf = selected / (selected + not_selected) return self.format_results(output, physical_perf, logical_perf) def _check_input_svd(self, svd: SVDistribution) -> tuple[float, float]: """ Check the mixed input state for its validity in the sampling case (no superposed states allowed) and compute both Zero Photon Probability (zpp) and MAX Probability of input states containing enough photons (max_p). zpp is used to compute samples/shots ratio. max_p is used to compute a threshold to ignore non-probable input states. """ if self._detectors: assert len(self._detectors) == svd.m, \ f"State length ({svd.m}) and detector count ({len(self._detectors)}) do not match" zpp = 0 max_p = 0 for sv, p in svd.items(): if len(sv) > 1: raise ValueError(f"Noisy sampling does not support superposed states, got {sv}") n_photons = next(iter(sv.n)) # Number of photons in the (non superposed) state vector if n_photons == 0: zpp += p if n_photons >= self.min_detected_photons_filter: max_p = max(max_p, p) return zpp, max_p def _preprocess_input_state(self, svd: SVDistribution, max_p: float, n_threshold: int ) -> tuple[SVDistribution, float]: """ Rework the input distribution to get rid of improbable states. Compute a first value for physical performance """ p_threshold = max_p / n_threshold new_input = SVDistribution() physical_perf = 1 for sv, p in svd.items(): n_photons = next(iter(sv.n)) if n_photons < self.min_detected_photons_filter: physical_perf -= p elif p >= p_threshold: new_input[StateVector(sv[0])] = p new_input.normalize() get_logger().debug( f"Reduced input SVD from {len(svd)} to {len(new_input)} elements using {p_threshold} threshold", channel.general) return new_input, physical_perf def _compute_samples_with_perf(self, prepare_samples: int, physical_perf: float, zpp: float, max_shots: int) \ -> tuple[int, int]: if self.min_detected_photons_filter >= 2 and max_shots is not None: # This is cheating, but we need it if we want a good approximation of the number of shots to simulate max_shots *= physical_perf / (1 - zpp) # = P(n >= filter | n > 0) max_shots = math.ceil(max_shots) prepare_samples = min(max_shots, prepare_samples) return prepare_samples, max_shots def _prepare_provider(self, provider: SamplesProvider, svd: SVDistribution | tuple[Source, FockState], max_samples: int, max_shots: int, progress_callback: callable): pre_physical_perf = 1 prepare_samples = self.compute_samples(max_samples, max_shots) first_batch = [] sample_generator = None n = 0 source_defined = isinstance(svd, tuple) if prepare_samples: if source_defined: source, bs_input = svd n = bs_input.n sampler = source.create_sampler(bs_input, self.min_detected_photons_filter) pre_physical_perf = sampler.physical_perf zpp = sampler.zpp prepare_samples, max_shots = self._compute_samples_with_perf(prepare_samples, pre_physical_perf, zpp, max_shots) sample_generator = sampler.generate_separated_samples first_batch = provider.estimate_weights_from_source(sample_generator, prepare_samples) else: n = svd.n_max zpp, max_p = self._check_input_svd(svd) trimmed_svd, pre_physical_perf = self._preprocess_input_state(svd, max_p, prepare_samples) prepare_samples, max_shots = self._compute_samples_with_perf(prepare_samples, pre_physical_perf, zpp, max_shots) sample_generator = lambda i: [state.separate_state() if isinstance(state, NoisyFockState) else [state] for state_v in trimmed_svd.sample(i, non_null=False) for state in state_v.keys()] provider.estimate_weights_from_distribution(trimmed_svd, prepare_samples) # Prepare pools of pre-computed samples provider.prepare(progress_callback) return first_batch, pre_physical_perf, n, max_shots, sample_generator if prepare_samples else None @staticmethod def compute_samples(max_samples: int, max_shots: int) -> int: prepare_samples = max_samples if max_shots is not None: prepare_samples = min(max_samples, max_shots) return prepare_samples def samples(self, svd: SVDistribution | tuple[Source, FockState], max_samples: int, max_shots: int = None, progress_callback: callable = None) -> dict: """ Run a noisy sampling simulation and retrieve the results :param svd: The noisy input, expressed as a mixed state, or a tuple containing the source and the perfect input state :param max_samples: Max expected samples of interest in the results :param max_shots: Shots limit before the sampling ends (you might get fewer samples than expected) :param progress_callback: A progress callback :return: A dictionary of the form { "results": BSSamples, "physical_perf": float, "logical_perf": float } - results is the post-selected output state sample stream - physical_perf is the performance computed from the detected photon filter - logical_perf is the performance computed from the post-selection """ if not check_heralds_detectors(self._heralds, self._detectors): return self.format_results(BSSamples(), 1, 0) source_defined = isinstance(svd, tuple) # The best case scenario is a perfect sampling => use the "highway" code det_type = get_detection_type(self._detectors) one_input = len(svd) == 1 or (source_defined and svd[0].is_perfect()) if not self._heralds and not self._postselect.has_condition and one_input and det_type == DetectionType.PNR: only_input = svd[1] if source_defined else next(iter(svd))[0] if isinstance(only_input, FockState): get_logger().debug("Perfect sampling: use the fast '_perfect_samples_no_selection' call", channel.general) # Choose a consistent samples limit prepare_samples = self.compute_samples(max_samples, max_shots) if prepare_samples == 0: return self.format_results(BSSamples(), 1, 1) return self._perfect_sampling_no_selection(only_input, prepare_samples, progress_callback) provider = SamplesProvider(self._backend) provider.sleep_between_batches = self.sleep_between_batches first_batch, pre_physical_perf, n, max_selected_shots, sample_generator = self._prepare_provider(provider, svd, max_samples, max_shots, progress_callback) if sample_generator is None: return self.format_results(BSSamples(), 0, 1) res = self._noisy_sampling(sample_generator, provider, max_samples, max_selected_shots, det_type, first_batch, progress_callback) if self._compute_physical_logical_perf: res['physical_perf'] *= pre_physical_perf res['global_perf'] *= pre_physical_perf self.log_resources(sys._getframe().f_code.co_name, { 'n': n, 'max_samples': max_samples, 'max_shots': max_shots}) return res def sample_count(self, svd: SVDistribution | tuple[Source, BasicState], max_samples: int, max_shots: int = None, progress_callback: callable = None) -> dict: """ Run a noisy sampling simulation and retrieve the results :param svd: The noisy input, expressed as a mixed state, or a tuple containing the source and the perfect input state :param max_samples: Max expected samples of interest in the results :param max_shots: Shots limit before the sampling ends (you might get fewer samples than expected) :param progress_callback: A progress callback :return: A dictionary of the form { "results": BSCount, "physical_perf": float, "logical_perf": float } - results is the post-selected output state sample count - physical_perf is the performance computed from the detected photon filter - logical_perf is the performance computed from the post-selection """ sampling = self.samples(svd, max_samples, max_shots, progress_callback) sampling['results'] = samples_to_sample_count(sampling['results']) return sampling def log_resources(self, method: str, extra_parameters: dict): """Log resources of the noisy sampling simulator :param method: name of the method used :param extra_parameters: extra parameters to log Extra parameter can be: - max_samples - max_shots """ extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None} my_dict = { 'layer': 'NoisySamplingSimulator', 'backend': self._backend.name, 'm': self._backend._circuit.m, 'method': method } if extra_parameters: my_dict.update(extra_parameters) get_logger().log_resources(my_dict) def format_results(self, results, physical_perf, logical_perf): """ Format the simulation results by computing the global performance, and returning the physical and logical performances only if needed. :param results: the simulation results :param physical_perf: the physical performance :param logical_perf: the logical performance """ result = {'results': results, 'global_perf': physical_perf * logical_perf} if self._compute_physical_logical_perf: result['physical_perf'] = physical_perf result['logical_perf'] = logical_perf return result ================================================ FILE: perceval/simulators/polarization_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .simulator_interface import ASimulatorDecorator from perceval.utils import convert_polarized_state, Annotation, AnnotatedFockState, StateVector, SVDistribution, BSDistribution from perceval.utils.logging import channel, get_logger from perceval.components import Unitary, IDetector, DetectionType, get_detection_type class PolarizationSimulator(ASimulatorDecorator): def __init__(self, simulator): super().__init__(simulator) self._upol = None def _prepare_input(self, input_state): is_svd = False if isinstance(input_state, SVDistribution) and len(input_state) == 1: is_svd = True temp_sv = list(input_state.keys())[0] if len(temp_sv) == 1: input_state = temp_sv[0] if not isinstance(input_state, AnnotatedFockState): raise NotImplementedError("Polarization simulator can only process AnnotatedFockState inputs") spatial_input, preprocess_matrix = convert_polarized_state(input_state) circuit = Unitary(self._upol @ preprocess_matrix) self._simulator.set_circuit(circuit) if is_svd: spatial_input = SVDistribution(spatial_input) return spatial_input def set_circuit(self, circuit, m=None): self._prepare_circuit(circuit) def _prepare_circuit(self, circuit, m=None): self._upol = circuit.compute_unitary(use_polarization=True) def _prepare_detectors_impl(self, detectors: list[IDetector]): if get_detection_type(detectors) != DetectionType.PNR: get_logger().warn("Cannot use detectors in polarized circuits; giving PNR results", channel.user) return None def _split_odd_even(self, fs): s_odd = fs[1: fs.m: 2] s_even = fs[0: fs.m: 2] return s_odd, s_even def _postprocess_sv_impl(self, results: StateVector) -> StateVector: output = StateVector() for out_state, out_amplitude in results: s_odd, s_even = self._split_odd_even(out_state) # Keep annotations s_even = s_even.inject_annotation(Annotation("P:H")) s_odd = s_odd.inject_annotation(Annotation("P:V")) reduced_out_state = s_odd.merge(s_even) output += out_amplitude * reduced_out_state return output def _postprocess_bsd_impl(self, results: BSDistribution) -> BSDistribution: output = BSDistribution() for out_state, output_prob in results.items(): s_odd, s_even = self._split_odd_even(out_state) reduced_out_state = s_odd.merge(s_even) output[reduced_out_state] += output_prob return output def set_min_detected_photons_filter(self, value: int): super().set_min_detected_photons_filter(value) # Transmit value to next layer self._min_detected_photons_filter = 0 # Photon count is kept, no need to filter results in this layer ================================================ FILE: perceval/simulators/simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sys from collections import defaultdict from exqalibur import SimpleSourceIterator from multipledispatch import dispatch from numbers import Number from scipy.sparse import csc_array, csr_array from perceval.backends import AStrongSimulationBackend from perceval.components import ACircuit, IDetector, get_detection_type, DetectionType, check_heralds_detectors, Source from perceval.utils import BasicState, FockState, NoisyFockState, BSDistribution, StateVector, SVDistribution, PostSelect, global_params, \ DensityMatrix, post_select_distribution, post_select_statevector, partial_progress_callable from perceval.utils.density_matrix_utils import extract_upper_triangle from perceval.utils.logging import get_logger from perceval.runtime import cancel_requested from ._simulator_utils import _to_bsd, _inject_annotation, _merge_sv, _annot_state_mapping, _split_by_photon_and_tag_count, \ _list_merge, _separate_state from ._simulate_detectors import simulate_detectors from .simulator_interface import ISimulator class Simulator(ISimulator): """ A simulator class relying on a probability amplitude capable backend to simulate the output of a unitary non-polarized circuit given an BasicState, StateVector or SVDistribution input. The simulator is able to evolve or simulate the sampling a states with annotated photons. :param backend: A probability amplitude capable backend object """ detector_cb_start = 0.9 def __init__(self, backend: AStrongSimulationBackend): super().__init__() self._can_use_mask = False self._backend = backend self._invalidate_cache() self._logical_perf: float = 1 self._rel_precision: float = 1e-6 # Precision relative to the highest probability of interest in probs_svd self._keep_heralds = True self._n_heralds = 0 self._no_mask_heralded_modes = [] @property def precision(self): return self._rel_precision @precision.setter def precision(self, value: float): assert isinstance(value, Number) and value >= 0., "Precision must be a positive number" self._rel_precision = value def set_precision(self, precision: float): """ Set the precision of the simulator. When using probs_svd, states having a probability inferior to the precision times the highest probability will be discarded. """ self.precision = precision def set_heralds(self, heralds): """ Set the output heralds of the simulator. Any output that does not match the heralds will be discarded. Only used in probs, probs_svd, evolve, evolve_svd """ self._invalidate_cache() self._n_heralds = sum(heralds.values()) super().set_heralds(heralds) def keep_heralds(self, value: bool): """ Tells the simulator to keep or discard ancillary modes in output states :param value: True to keep ancillaries/heralded modes, False to discard them (default is keep). """ self._keep_heralds = value @property def logical_perf(self): return self._logical_perf def set_postselection(self, postselect: PostSelect): """Set a post-selection function :param postselect: a PostSelect object """ self._postselect = postselect def clear_postselection(self): """Clear the post-selection function""" self._postselect = PostSelect() def clear_heralds(self): self._invalidate_cache() self._heralds = {} self._n_heralds = 0 def set_circuit(self, circuit: ACircuit, m = None): """Set a circuit for simulation. :param circuit: a unitary circuit without polarized components :param m: The number of modes in the circuit. Only used in LC and TD simulators. If not provided, it is inferred from the modes of the components of the circuit list. """ self._invalidate_cache() self._backend.set_circuit(circuit) @dispatch(FockState, FockState) def prob_amplitude(self, input_state: FockState, output_state: FockState) -> complex: """Compute the probability amplitude of an output fock state versus an input fock state. :param input_state: A fock state :param output_state: A fock state :return: The complex probability amplitude """ if input_state.n == 0: return complex(1) if output_state.n == 0 else complex(0) self._backend.set_input_state(input_state) return self._backend.prob_amplitude(output_state) @dispatch(NoisyFockState, NoisyFockState) def prob_amplitude(self, input_state: NoisyFockState, output_state: NoisyFockState) -> complex: """Compute the probability amplitude of an output fock state versus an input fock state. :param input_state: A noisy fock state :param output_state: A noisy fock state. >>> simulator.set_circuit(Circuit(1)) # One mode identity >>> simulator.prob_amplitude(NoisyFockState('|{0}>'), NoisyFockState('|{1}>')) 0 >>> simulator.prob_amplitude(NoisyFockState('|{0}>'), NoisyFockState('|{0}>')) 1 :return: The complex probability amplitude """ if input_state.n == 0: return complex(1) if output_state.n == 0 else complex(0) input_map = _annot_state_mapping(input_state) output_map = _annot_state_mapping(output_state) if len(input_map) != len(output_map): return complex(0) probampli = 1 for annot, in_s in input_map.items(): if annot not in output_map: return complex(0) self._backend.set_input_state(in_s) probampli *= self._backend.prob_amplitude(output_map[annot]) return probampli @dispatch(NoisyFockState, FockState) def prob_amplitude(self, input_state: NoisyFockState, output_state: FockState) -> complex: """Compute the probability amplitude of an output fock state versus an input fock state. :param input_state: A fock state with or without photon annotations :param output_state: A fock state with or without photon annotations. If the input state holds annotations, the output state must hold the same ones, otherwise the computed probability amplitude is 0. :return: The complex probability amplitude """ return complex(0) @dispatch(FockState, NoisyFockState) def prob_amplitude(self, input_state: FockState, output_state: NoisyFockState) -> complex: """Compute the probability amplitude of an output fock state versus an input fock state. :param input_state: A fock state with or without photon annotations :param output_state: A fock state with or without photon annotations. If the input state holds annotations, the output state must hold the same ones, otherwise the computed probability amplitude is 0. :return: The complex probability amplitude """ return complex(0) @dispatch(StateVector, FockState) def prob_amplitude(self, input_state: StateVector, output_state: FockState) -> complex: result = complex(0) for state, pa in input_state: result += self.prob_amplitude(state, output_state) * pa return result @dispatch(StateVector, NoisyFockState) def prob_amplitude(self, input_state: StateVector, output_state: NoisyFockState) -> complex: result = complex(0) for state, pa in input_state: result += self.prob_amplitude(state, output_state) * pa return result @dispatch(FockState, FockState) def probability(self, input_state: FockState, output_state: FockState) -> float: """Compute the probability of an output fock state versus an input fock state, simulating a measure. This call does not take heralding, post-selection or detector types into account :param input_state: A fock state with or without photon annotations :param output_state: A fock state, annotations are ignored :return: The probability (float between 0 and 1) """ if input_state.n == 0: return 1 if output_state.n == 0 else 0 input_list = [input_state] result = 0 for p_output_state in output_state.partition( [input_state.n for input_state in input_list]): prob = 1 for i_state, o_state in zip(input_list, p_output_state): self._backend.set_input_state(i_state) prob *= self._backend.probability(o_state) result += prob return result @dispatch(NoisyFockState, FockState) def probability(self, input_state: NoisyFockState, output_state: FockState) -> float: """Compute the probability of an output fock state versus an input fock state, simulating a measure. This call does not take heralding, post-selection or detector types into account :param input_state: A fock state with or without photon annotations :param output_state: A fock state, annotations are ignored :return: The probability (float between 0 and 1) """ if input_state.n == 0: return 1 if output_state.n == 0 else 0 input_list = input_state.separate_state() result = 0 for p_output_state in output_state.partition( [input_state.n for input_state in input_list]): prob = 1 for i_state, o_state in zip(input_list, p_output_state): self._backend.set_input_state(i_state) prob *= self._backend.probability(o_state) result += prob return result @dispatch(StateVector, FockState) def probability(self, input_state: StateVector, output_state: FockState) -> float: sv_out = self.evolve(input_state) # This is not as optimized as it could be result = 0 for state, pa in sv_out: if isinstance(state, NoisyFockState): state = state.clear_annotations() if state == output_state: result += abs(pa) ** 2 return result @dispatch(StateVector, NoisyFockState) def probability(self, input_state: StateVector, output_state: NoisyFockState) -> float: output_state = output_state.clear_annotations() sv_out = self.evolve(input_state) # This is not as optimized as it could be result = 0 for state, pa in sv_out: if isinstance(state, NoisyFockState): state = state.clear_annotations() if state == output_state: result += abs(pa) ** 2 return result def _invalidate_cache(self): self._evolve: dict[BasicState | tuple[BasicState, int], StateVector] = {} self.DEBUG_evolve_count = 0 self.DEBUG_merge_count = 0 def _evolve_cache(self, input_list: set[FockState]): for state in input_list: if state not in self._evolve: self._backend.set_input_state(state) self._evolve[state] = self._backend.evolve() self.DEBUG_evolve_count += 1 def _evolve_cache_with_n(self, input_list: set[tuple[FockState, int]], non_pnr_detector_modes: list[int] | None = None): previous_n = None for state, n in sorted(input_list, key=lambda x: x[1]): if (state, n) not in self._evolve: if n != previous_n and n != 0: previous_n = n # The backend init the mask when setting the state and when setting the mask if there is an input state, # no need to do it twice self._backend._input_state = None self.use_mask(n, non_pnr_detector_modes) self._backend.set_input_state(state) self._evolve[(state, n)] = self._backend.evolve() self.DEBUG_evolve_count += 1 def _merge_probability_dist(self, input_list: list[FockState]) -> BSDistribution: distributions = [_to_bsd(self._evolve[input_state]) for input_state in input_list] self.DEBUG_merge_count += len(distributions) - 1 return BSDistribution.list_tensor_product(distributions, merge_modes=True) @dispatch(FockState) def probs(self, input_state: FockState) -> BSDistribution: """ Compute the probability distribution from a state input :param input_state: The input fock state or state vector :return: The post-selected output state distribution (BSDistribution) """ input_list = [input_state] self._evolve_cache(set(input_list)) result = self._merge_probability_dist(input_list) result, self._logical_perf = post_select_distribution(result, self._postselect, self._heralds, self._keep_heralds) return result @dispatch(NoisyFockState) def probs(self, input_state: NoisyFockState) -> BSDistribution: """ Compute the probability distribution from a state input :param input_state: The input fock state or state vector :return: The post-selected output state distribution (BSDistribution) """ input_list = input_state.separate_state() self._evolve_cache(set(input_list)) result = self._merge_probability_dist(input_list) result, self._logical_perf = post_select_distribution(result, self._postselect, self._heralds, self._keep_heralds) return result @dispatch(StateVector) def probs(self, input_state: StateVector) -> BSDistribution: if len(input_state) == 1: return self.probs(input_state[0]) return _to_bsd(self.evolve(input_state)) def _probs_svd_generic(self, input_dist, p_threshold, non_pnr_detector_modes: list[int] | None, progress_callback: callable = None): """decomposed input: From a SVD = { pa_11*bs_11 + ... + pa_n1*bs_n1: p1, pa_12*bs_12 + ... + pa_n2*bs_n2: p2, ... pa_1k*bs_1k + ... + pa_nk*bs_nk: pk } the following data structure is built: [ (p1, [ (pa_11, {annot_11*: bs_11*,..}), ... (pa_n1, {annot_n1*: bs_n1*,..}) ] ), ... (pk, [ (pa_1k, {annot_1k*: bs_1k*,..}), ... (pa_nk, {annot_nk*: bs_nk*,..}) ] ) ] where {annot_xy*: bs_xy*,..} is a mapping between an annotation and a pure basic state""" decomposed_input = [(prob, [(pa, _annot_state_mapping(st)) for st, pa in sv], next(iter(sv.n))) for sv, prob in input_dist.items()] # sv.n has only one element # If sv.n >= state.n + n_heralds, we get all the Fock space in the result input_set = set([(state, self._best_n(s[2], state.n)) for s in decomposed_input for t in s[1] for state in t[1].values()]) self._evolve_cache_with_n(input_set, non_pnr_detector_modes) """Reconstruct output probability distribution""" res = BSDistribution() for idx, (prob0, sv_data, n) in enumerate(decomposed_input): """First, recombine evolved state vectors given a single input""" result_sv = StateVector() for probampli, instate_list in sv_data: prob_sv = abs(probampli) ** 2 evolved_in_s = StateVector() for annot, in_s in instate_list.items(): cached_res = _inject_annotation(self._evolve[(in_s, self._best_n(n, in_s.n))], annot) evolved_in_s = _merge_sv(evolved_in_s, cached_res, prob_threshold=p_threshold / (10 * prob_sv * prob0)) if len(evolved_in_s) == 0: break self.DEBUG_merge_count += 1 if evolved_in_s: result_sv += probampli * evolved_in_s """Then, add the resulting distribution for a single input to the global distribution""" for bs, p in _to_bsd(result_sv).items(): prob = p * prob0 res[bs] += prob self._logical_perf += prob if progress_callback: exec_request = progress_callback((idx + 1) / len(decomposed_input), 'probs') if cancel_requested(exec_request): raise RuntimeError("Cancel requested") if len(res): res.normalize() return res def _probs_svd_fast(self, input_dist, p_threshold, non_pnr_detector_modes: list[int] | None, progress_callback: callable = None): """decomposed input: From a SVD = { bs_1: p1, bs_2: p2, ... bs_k: pk } the following data structure is built: [ (p1, [bs_1,]), ... (pk, [bs_k,]) ] where [bs_x,] is the list of the un-annotated separated basic state (result of bs_x.separate_state()) """ if isinstance(input_dist, SVDistribution): decomposed_input = [(prob, _separate_state(sv[0]), sv[0].n) for sv, prob in input_dist.items()] else: decomposed_input = [(prob, states, sum(state.n for state in states)) for states, prob in input_dist] if len(decomposed_input) == 1 and len(decomposed_input[0][1]) == 1: # Shortcut: avoid recombination state = decomposed_input[0][1][0] n = decomposed_input[0][2] n = self._best_n(n, n) self.use_mask(n, non_pnr_detector_modes) self._backend.set_input_state(state) res = self._backend.prob_distribution() self._logical_perf += sum(res.values()) * decomposed_input[0][0] if len(res): res.normalize() return res """Create a cache with strong simulation of all unique input""" cache = {} input_dict = defaultdict(float) for (prob, states, n) in decomposed_input: for state in states: s = (state, self._best_n(n, state.n)) current_prob = input_dict[s] if prob > current_prob: input_dict[s] = prob len_input_set = len(input_dict) prog_cb = partial_progress_callable(progress_callback, max_val=0.5) # From 0. to 0.5 previous_n = None for idx,((state, n), prob0) in enumerate(sorted(input_dict.items(), key=lambda x: x[0][1])): if n != previous_n and n != 0: previous_n = n # The backend init the mask when setting the state and when setting the mask if there is an input state, # no need to do it twice self._backend._input_state = None self.use_mask(n, non_pnr_detector_modes) self._backend.set_input_state(state) cache[(state, n)] = self._backend.prob_iterator(p_threshold / (10 * prob0)) if prog_cb and idx % 10 == 0: progress = (idx + 1) / len_input_set exec_request = prog_cb(progress, 'compute probability distributions') if cancel_requested(exec_request): raise RuntimeError("Cancel requested") """Reconstruct output probability distribution""" res = BSDistribution() prog_cb = partial_progress_callable(progress_callback, min_val=0.5) # From 0.5 to 1 for idx, (prob0, bs_data, n) in enumerate(decomposed_input): """First, recombine evolved state vectors given a single input""" probs_in_s = _list_merge([cache[(state, self._best_n(n, state.n))] for state in bs_data], prob_threshold=p_threshold / (10 * prob0)) self.DEBUG_merge_count += len(bs_data) - 1 """ 1st step of computing logical performance: When using a mask, the sum of output probs sum(probs_in_s.values()) can be < 1. In this case add the success rate (the sum), weighted by the probability (prob0) of the input state to appear in the mixed state (input_dist, reworked to decomposed_input). """ self._logical_perf += sum(probs_in_s.values()) * prob0 """Then, add the resulting distribution to the global distribution""" for bs, p in probs_in_s.items(): res[bs] += p * prob0 if prog_cb and idx % 20 == 0: progress = (idx + 1) / len(decomposed_input) exec_request = prog_cb(progress, 'recombine distributions') if cancel_requested(exec_request): raise RuntimeError("Cancel requested") """ 2nd step of computing logical performance: After the whole mixed state (input_dist) has been simulated, the `_logical_perf` is the probability of getting a state that is both physically and logically accepted. To get the probability of getting a state that is logically accepted knowing that it was physically accepted, we need to divide the current logical perf value by the physical perf """ if len(res): res.normalize() return res def _get_prob_threshold(self, max_p: float) -> float: return max(global_params['min_p'], max_p * self._rel_precision) def _preprocess_iterator(self, svd: tuple[Source, FockState]) -> tuple[SimpleSourceIterator, float, float]: iterator = svd[0].create_iterator(svd[1], self.min_detected_photons_filter) iterator.prob_threshold = self._get_prob_threshold(iterator.max_p) return iterator, iterator.prob_threshold, iterator.physical_perf def _preprocess_svd(self, svd: SVDistribution) -> tuple[SVDistribution, float, bool, float]: """Trim input SVD given _rel_precision threshold and extract characteristics from it""" max_p = 0 to_remove = set() # O(1) to test __contains__ against O(n) for a list phys_perf = 1 # We need to work on a copy. Let's do a first trim as copy for sv, p in svd.items(): if max(sv.n) >= self.min_detected_photons_filter: max_p = max(p, max_p) else: phys_perf -= p to_remove.add(sv) p_threshold = self._get_prob_threshold(max_p) if len(to_remove): trimmed_svd = SVDistribution({state: pr for state, pr in svd.items() if pr > p_threshold and state not in to_remove}) else: # Avoids hashing all states to test __contains__ trimmed_svd = SVDistribution({state: pr for state, pr in svd.items() if pr > p_threshold}) # At this stage, all states have at least one BS with enough photons to_remove = set() to_add = SVDistribution() for sv, p in trimmed_svd.items(): if len(sv) != 1 and len(sv.n) != 1: new_svd = _split_by_photon_and_tag_count(sv) to_remove.add(sv) for split_sv, ps in new_svd.items(): prob = p * ps # split_sv.n is a set so we can't use [0] if max(split_sv.n) >= self.min_detected_photons_filter: to_add[split_sv] += prob else: phys_perf -= prob if len(to_remove): for sv, p in to_add.items(): trimmed_svd[sv] += p max_p = max(trimmed_svd[sv], max_p) p_threshold = self._get_prob_threshold(max_p) trimmed_svd = SVDistribution({state: pr for state, pr in trimmed_svd.items() if pr > p_threshold and state not in to_remove}) has_superposed_states = any(len(sv) > 1 for sv in trimmed_svd) return trimmed_svd, p_threshold, has_superposed_states, phys_perf def probs_svd(self, input_dist: SVDistribution | tuple[Source, FockState], detectors: list[IDetector] = None, progress_callback: callable = None) -> dict[str, any]: """ Compute the probability distribution from a SVDistribution input and as well as performance scores :param input_dist: A state vector distribution describing the input to simulate :param detectors: An optional list of detectors :param progress_callback: A function with the signature `func(progress: float, message: str)` :return: A dictionary of the form { "results": BSDistribution, "physical_perf": float, "logical_perf": float } * results is the post-selected output state distribution * physical_perf is the performance computed from the detected photon filter * logical_perf is the performance computed from the post-selection """ if not check_heralds_detectors(self._heralds, detectors): return self.format_results(BSDistribution(), 1, 0) if isinstance(input_dist, SVDistribution): svd, p_threshold, has_superposed_states, physical_perf = self._preprocess_svd(input_dist) n = input_dist.n_max else: svd, p_threshold, physical_perf = self._preprocess_iterator(input_dist) has_superposed_states = False n = input_dist[1].n if input_dist[0].partially_distinguishable: n *= 2 is_pnr = get_detection_type(detectors) == DetectionType.PNR if detectors is not None: self._no_mask_heralded_modes = [m for m, d in enumerate(detectors) if self._heralds.get(m, None) == 0 and d is not None and d.efficiency < 1] self.init_use_mask(is_pnr) if is_pnr: prog_cb = progress_callback else: prog_cb = partial_progress_callable(progress_callback, max_val=self.detector_cb_start) self._logical_perf = 0 # All the modes where the mask must apply a "at least" rule non_pnr_detector_modes = [m for m, d in enumerate(detectors) if d is not None and d.type != DetectionType.PNR and self._heralds.get(m, 0) > 0] \ if self._can_use_mask and detectors else None if has_superposed_states: res = self._probs_svd_generic(svd, p_threshold, non_pnr_detector_modes, prog_cb) else: res = self._probs_svd_fast(svd, p_threshold, non_pnr_detector_modes, prog_cb) if self._logical_perf > 0 and physical_perf > 0: self._logical_perf /= physical_perf if not len(res): return self.format_results(res, physical_perf, 0) if detectors: prog_cb = partial_progress_callable(progress_callback, min_val=self.detector_cb_start) res, phys_perf = simulate_detectors(res, detectors, self.min_detected_photons_filter, p_threshold, self._heralds if not self._compute_physical_logical_perf else {}, prog_cb) physical_perf *= phys_perf res, logical_perf_contrib = post_select_distribution(res, self._postselect, self._heralds, self._keep_heralds) self._logical_perf *= logical_perf_contrib self._no_mask_heralded_modes = [] self.log_resources(sys._getframe().f_code.co_name, {'n': n}) return self.format_results(res, physical_perf, self._logical_perf) def _setup_heralds(self, n=None, non_pnr_detector_modes=None): # Set up a mask corresponding to heralds: mask_str = "" for i in range(self._backend._circuit.m): if i in self._heralds and i not in self._no_mask_heralded_modes: herald_expectation = self._heralds[i] if herald_expectation > 32: # FsMask limitation raise ValueError("Cannot simulate an herald expecting more than 32 detected photons") # Encodes expected photon count from 0x30 to 0x4F ASCII characters mask_str += f"{chr(0x30 + herald_expectation)}" else: mask_str += " " self._backend.set_mask(mask_str, n, non_pnr_detector_modes) def init_use_mask(self, is_pnr) -> None: self._can_use_mask = (any(m not in self._no_mask_heralded_modes for m in self._heralds) and (is_pnr or not self._compute_physical_logical_perf)) def use_mask(self, n=None, non_pnr_detector_modes=None): if self._can_use_mask: self._setup_heralds(n, non_pnr_detector_modes) elif self._backend._masks_str is not None: self._backend.clear_mask() def _best_n(self, n_ext: int, n_own: int) -> int: """Computes the number of photons to use in the mask for a given state to simulate. If n_ext >= n_own + n_heralds, we get all the Fock space in the result. :param n_ext: Number of photons of the state containing the state to simulate. :param n_own: Number of photons in the state to simulate. """ return min(n_ext, n_own + self._n_heralds) if self._can_use_mask else n_own + self._n_heralds def probs_density_matrix(self, dm: DensityMatrix) -> dict: """ gives the output probability distribution, after evolving some density matrix through the simulator :param dm: the input DensityMatrix :return: A dictionary of the form { "results": BSDistribution, "physical_perf": float, "logical_perf": float } * results is the post-selected output state distribution * physical_perf is the performance computed from the detected photon filter * logical_perf is the performance computed from the post-selection """ if not isinstance(dm, DensityMatrix): raise TypeError(f"dm must be a DensityMatrix object, {type(dm)} was given") input_list = self._get_density_matrix_input_list(dm) u_evolve = self._construct_evolve_operator(input_list, dm) # Here I change to csr format to be able to iterate on the rows u_evolve_in_row = csr_array(u_evolve) res_bsd = BSDistribution() physical_perf = 1 for row_idx, fs in enumerate(dm.inverse_index): vec = u_evolve_in_row[[row_idx]] prob = abs((vec @ dm.mat @ vec.conj().T)[0, 0]) if fs.n >= self.min_detected_photons_filter: res_bsd[fs] += prob else: physical_perf -= prob res_bsd, logical_perf_coeff = post_select_distribution( res_bsd, self._postselect, self._heralds, self._keep_heralds) return self.format_results(res_bsd, physical_perf, self._logical_perf * logical_perf_coeff) def _evolve_no_compute(self, decomposed_input, n_photons): """Uses the cached results to compute the evolution of the state described in decomposed_input""" result_sv = StateVector() for probampli, instate_map, n in decomposed_input: reslist = [] for annot in instate_map: in_s = instate_map[annot] if in_s.n == 0: reslist.append(in_s) continue reslist.append(_inject_annotation(self._evolve[(in_s, self._best_n(n, in_s.n))], annot)) # Recombine results for one basic state input evolved_in_s = reslist.pop(0) for sv in reslist: evolved_in_s = _merge_sv(evolved_in_s, sv) self.DEBUG_merge_count += 1 result_sv += evolved_in_s * probampli # result_sv is normalized here result_sv, self._logical_perf = post_select_statevector(result_sv, self._postselect, self._heralds, self._keep_heralds) self.log_resources("evolve", { 'n': n_photons if isinstance(n_photons, int) else max(n_photons)}) return result_sv def _prepare_decomposed_input(self, input_state: SVDistribution): """Decay input to a list of basic states without annotations and evolve each of them""" decomposed_input = [(pa, _annot_state_mapping(st), max(sv.n)) for sv in input_state for st, pa in sv] input_list = [(FockState(t[1][annot]), self._best_n(t[2], t[1][annot].n)) for t in decomposed_input for annot in t[1]] self._evolve_cache_with_n(set(input_list)) return decomposed_input def evolve(self, input_state: BasicState | StateVector) -> StateVector: """ Evolve a state through the circuit. If the simulator has logical selection, the performance for this input state is stored in self.logical_perf, and only the states matching the logical selection are kept. :param input_state: The input fock state or state vector :return: The output state vector """ decomposed_input = self._prepare_decomposed_input(SVDistribution(input_state)) result_sv = self._evolve_no_compute(decomposed_input, input_state.n) return result_sv def evolve_svd(self, svd: SVDistribution | StateVector | BasicState, progress_callback: callable = None) -> dict: """ Compute the SVDistribution evolved through a linear optics circuit :param svd: The input StateVector distribution :param progress_callback: A function with the signature `func(progress: float, message: str)` :return: A dictionary of the form { "results": SVDistribution, "physical_perf": float, "logical_perf": float } * results is the post-selected output SVDistribution * physical_perf is the performance computed from the detected photon filter * logical_perf is the performance computed from the post-selection """ if not isinstance(svd, SVDistribution): svd = SVDistribution(svd) self.init_use_mask(True) # No detectors when using evolve self._prepare_decomposed_input(svd) global_perf = 0 physical_perf = 0 new_svd = SVDistribution() for idx, (sv, p) in enumerate(svd.items()): # It is intended to reject if any of the component doesn't have enough photons if min(sv.n) >= self.min_detected_photons_filter: decomposed_input = [(pa, _annot_state_mapping(st), max(sv.n)) for st, pa in sv] new_sv = self._evolve_no_compute(decomposed_input, sv.n) success_prob = p * self._logical_perf global_perf += success_prob if new_sv.m != 0: new_svd[new_sv] += success_prob physical_perf += p if progress_callback: exec_request = progress_callback((idx + 1) / len(svd), 'evolve_svd') if cancel_requested(exec_request): raise RuntimeError("Cancel requested") self._logical_perf = global_perf / physical_perf if physical_perf != 0 else 0 if len(new_svd): new_svd.normalize() return self.format_results(new_svd, physical_perf, self._logical_perf) def evolve_density_matrix(self, dm: DensityMatrix) -> DensityMatrix: """ Compute the DensityMatrix evolved from "dm" through a linear optics circuit :param dm: The density Matrix to evolve :return: The evolved DensityMatrix """ if self._backend._masks_str is not None: self._backend.clear_mask() if not isinstance(dm, DensityMatrix): raise TypeError(f"dm must be of DensityMatrix type, {type(dm)} was given") # Establishing the set of FockState to evolve input_list = self._get_density_matrix_input_list(dm) u_evolve = self._construct_evolve_operator(input_list, dm) inter_matrix = u_evolve @ extract_upper_triangle(dm.mat) @ u_evolve.T.conj() out_matrix = inter_matrix + inter_matrix.T.conjugate(copy=False) return DensityMatrix(out_matrix, index=dm.index, check_hermitian=False) def _construct_evolve_operator(self, input_list: list[BasicState], dm: DensityMatrix) -> csc_array: """ construct the evolution operator needed to perform evolve_density_matrix. Stores it in a csc sparse_matrix """ u_evolve_data = [] u_evolve_indices = [] u_evolve_indptr = [0] nnz_count = 0 for i, fs in enumerate(dm.inverse_index): if fs in input_list: output_sv = self.evolve(fs) for state, amplitude in output_sv: u_evolve_data.append(amplitude) u_evolve_indices.append(dm.index[state]) nnz_count += 1 u_evolve_indptr.append(nnz_count) # Here we use csc array, because it is constructed row by row u_evolve = csc_array((u_evolve_data, u_evolve_indices, u_evolve_indptr), shape=dm.shape) return u_evolve @staticmethod def _get_density_matrix_input_list(dm: DensityMatrix) -> list: """ get the list of Fockstates on which a DensityMatrix is embedded """ input_list = [] for k in range(dm.size): if dm.mat[k, k] != 0: input_list.append(dm.inverse_index[k]) return input_list def log_resources(self, method: str, extra_parameters: dict): """Log resources of the simulator :param method: name of the method used :param extra_parameters: extra parameters to log Extra parameter can be: - n """ extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None} my_dict = { 'layer': 'Simulator', 'backend': self._backend.name, 'm': self._backend._circuit.m, 'method': method } if extra_parameters: my_dict.update(extra_parameters) get_logger().log_resources(my_dict) ================================================ FILE: perceval/simulators/simulator_factory.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .feed_forward_simulator import FFSimulator from .simulator_interface import ISimulator from .simulator import Simulator from .delay_simulator import DelaySimulator from .loss_simulator import LossSimulator from .polarization_simulator import PolarizationSimulator from ._simulator_utils import _unitary_components_to_circuit from perceval.components import ACircuit, TD, LC, Processor, Experiment, AFFConfigurator from perceval.backends import ABackend, SLOSBackend, BACKEND_LIST class SimulatorFactory: """ Using the SimulatorFactory is an easy and integrated way of instantiating the correct layers of simulation for a given circuit. The factory will adapt to the component needs, in terms of simulation, and chain the correct simulator calls. """ @staticmethod def build(circuit: ACircuit | Processor | list, backend: ABackend | str = None, **kwargs) -> ISimulator: """ :param circuit: The optical circuit to build the simulation layers around. The circuit can be a unitary circuit (Circuit object), a list containing positioned unitary components + LC + TD, or a Processor object. :param backend: (Optional) Any probampli capable backend instance or name. If no backend is passed, then the processor backend name is used if the first parameter's type is Processor. Ultimately, the fallback is a SLOS backend instantiated without any configuration (i.e. no mask) :param kwargs: If backend is a string, the kwargs are transmitted to the instantiation of the backend. :return: A simulator object with the input circuit set """ sim_polarization = False sim_delay = False sim_losses = False sim_feed_forward = False convert_to_circuit = False min_detected_photons = None post_select = None heralds = None noise = None m = None if isinstance(circuit, Processor): if backend is None: # If no backend was chosen, the backend type set in the Processor is used backend = circuit.backend circuit = circuit.experiment if not isinstance(circuit, ACircuit): convert_to_circuit = True if isinstance(circuit, Experiment): m = circuit.circuit_size min_detected_photons = circuit.min_photons_filter post_select = circuit.post_select_fn heralds = circuit.heralds noise = circuit.noise if circuit.is_unitary: circuit = circuit.unitary_circuit(use_phase_noise=True) else: circuit = circuit.components if not isinstance(circuit, ACircuit): for _, cp in circuit: if not sim_losses and isinstance(cp, LC): sim_losses = True convert_to_circuit = False if not sim_delay and isinstance(cp, TD): sim_delay = True convert_to_circuit = False if not sim_polarization and isinstance(cp, ACircuit): sim_polarization = cp.requires_polarization if not sim_feed_forward and isinstance(cp, AFFConfigurator): sim_feed_forward = True convert_to_circuit = False if isinstance(circuit, ACircuit): sim_polarization = circuit.requires_polarization if backend is None: backend = SLOSBackend() # The default is SLOS if isinstance(backend, str): if backend in BACKEND_LIST: backend = BACKEND_LIST[backend](**kwargs) # Create an instance of the backend else: raise ValueError(f"Backend '{backend}' not supported") # Building the simulator layers if sim_feed_forward: assert not sim_delay, "Cannot simulate time delays in a feed-forward simulation" assert not sim_polarization, "Cannot simulate polarization in a feed-forward simulation" simulator = FFSimulator(backend) simulator.set_noise(noise) else: simulator = Simulator(backend) if sim_polarization: simulator = PolarizationSimulator(simulator) if sim_delay: simulator = DelaySimulator(simulator) if sim_losses: simulator = LossSimulator(simulator) simulator.set_selection(min_detected_photons_filter=min_detected_photons, postselect=post_select, heralds=heralds) if convert_to_circuit: circuit = _unitary_components_to_circuit(circuit, m) # m can only be retrieved from a Processor (and useful only in that case) simulator.set_circuit(circuit, m) return simulator ================================================ FILE: perceval/simulators/simulator_interface.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from abc import ABC, abstractmethod from perceval.components import ACircuit, IDetector from perceval.utils import BSDistribution, StateVector, SVDistribution, PostSelect, post_select_distribution, \ post_select_statevector, filter_distribution_photon_count class ISimulator(ABC): def __init__(self): self._keep_heralds = True self._silent = False self._postselect: PostSelect = PostSelect() self._heralds: dict = {} self._min_detected_photons_filter: int = 0 self._compute_physical_logical_perf = False def set_silent(self, silent: bool): self._silent = silent @abstractmethod def set_circuit(self, circuit, m = None): pass @abstractmethod def probs(self, input_state) -> BSDistribution: pass @abstractmethod def probs_svd(self, svd: SVDistribution, detectors: list[IDetector] = None, progress_callback: callable = None) -> dict: pass @abstractmethod def evolve(self, input_state) -> StateVector: pass def set_min_detected_photons_filter(self, value: int): """ Set a minimum number of detected photons in the output distribution, counting only the non-heralded modes. :param value: The minimum photon count """ self._min_detected_photons_filter = value def set_precision(self, precision: float): pass def set_heralds(self, heralds: dict[int, int]): self._heralds = heralds def set_selection(self, min_detected_photons_filter: int = None, postselect: PostSelect = None, heralds: dict[int, int] = None): """ Set the min_detected_photons_filter, postselect, and heralds, if defined. :param min_detected_photons_filter: The minimum photon count. :param postselect: The postselect to apply at the end of the computation. :param heralds: The heralds to apply at the end of the computation. Only the output heralds are considered here. dictionary of the form {mode: expected} """ if min_detected_photons_filter is not None: self.set_min_detected_photons_filter(min_detected_photons_filter) if postselect is not None: self._postselect = postselect if heralds is not None: self.set_heralds(heralds) @property def min_detected_photons_filter(self) -> int: """ The simulated minimum number of photons that a state needs to have to be counted as valid. Includes the expected photons from the heralds. """ return self._min_detected_photons_filter + sum(self._heralds.values()) def keep_heralds(self, value: bool): """ Tells the simulator to keep or discard ancillary modes in output states :param value: True to keep ancillaries/heralded modes, False to discard them (default is keep). """ self._keep_heralds = value def compute_physical_logical_perf(self, value: bool): """ Tells the simulator to compute or not the physical and logical performances when possible :param value: True to compute the physical and logical performances, False otherwise. """ self._compute_physical_logical_perf = value def format_results(self, results: dict(), physical_perf: float, logical_perf: float): """ Format the simulation results by computing the global performance, and returning the physical and logical performances only if needed. :param results: the simulation results :param physical_perf: the physical performance :param logical_perf: the logical performance """ result = {'results': results, 'global_perf': physical_perf * logical_perf} if self._compute_physical_logical_perf: result['physical_perf'] = physical_perf result['logical_perf'] = logical_perf return result class ASimulatorDecorator(ISimulator, ABC): _can_transmit_selection = False def __init__(self, simulator: ISimulator): super().__init__() self._simulator: ISimulator = simulator @abstractmethod def _prepare_input(self, input_state): pass @abstractmethod def _prepare_circuit(self, circuit, m = None) -> ACircuit: pass @abstractmethod def _postprocess_bsd_impl(self, bsd: BSDistribution) -> BSDistribution: pass @abstractmethod def _postprocess_sv_impl(self, sv: StateVector) -> StateVector: pass @abstractmethod def _prepare_detectors_impl(self, detectors: list[IDetector]) -> list[IDetector] | None: pass def _transmit_heralds_postselect(self): pass def _prepare_detectors(self, detectors: list[IDetector] = None) -> list[IDetector] | None: if detectors is None: return None return self._prepare_detectors_impl(detectors) def _postprocess_bsd(self, results: BSDistribution): results = self._postprocess_bsd_impl(results) physical_perf = 1 if self.min_detected_photons_filter: results, physical_perf = filter_distribution_photon_count(results, self.min_detected_photons_filter) logical_perf = 1 if (self._compute_physical_logical_perf or not self._can_transmit_selection) and (self._postselect.has_condition or self._heralds): # Only at last layer that can't transfer this responsibility to the next layer results, logical_perf = post_select_distribution(results, self._postselect, self._heralds, self._keep_heralds) elif self._heralds and not self._keep_heralds: new_res = BSDistribution() for bs, p in results.items(): new_res[bs.remove_modes(list(self._heralds))] += p results = new_res return results, logical_perf, physical_perf def _postprocess_sv(self, sv: StateVector) -> StateVector: sv = self._postprocess_sv_impl(sv) if self._postselect.has_condition or self._heralds: sv, _ = post_select_statevector(sv, self._postselect, self._heralds, self._keep_heralds) return sv def compute_physical_logical_perf(self, value: bool): super().compute_physical_logical_perf(value) self._simulator.compute_physical_logical_perf(value) def set_circuit(self, circuit, m=None): self._simulator.set_circuit(self._prepare_circuit(circuit, m)) def probs(self, input_state) -> BSDistribution: if self._can_transmit_selection: self._transmit_heralds_postselect() results = self._simulator.probs(self._prepare_input(input_state)) results = self._postprocess_bsd(results)[0] return results def probs_svd(self, svd: SVDistribution, detectors=None, progress_callback: callable = None) -> dict: if not self._simulator._compute_physical_logical_perf and self._can_transmit_selection: self._transmit_heralds_postselect() probs = self._simulator.probs_svd(self._prepare_input(svd), detectors=self._prepare_detectors(detectors), progress_callback=progress_callback) probs['results'], logical_perf_coeff, physical_perf_coeff = self._postprocess_bsd(probs['results']) if self._simulator._compute_physical_logical_perf: return self._simulator.format_results(probs['results'], probs['physical_perf'] * physical_perf_coeff, probs['logical_perf'] * logical_perf_coeff) return {'results': probs['results'], 'global_perf': probs['global_perf'] * physical_perf_coeff * logical_perf_coeff} def evolve(self, input_state) -> StateVector: results = self._simulator.evolve(self._prepare_input(input_state)) return self._postprocess_sv(results) def set_min_detected_photons_filter(self, value: int): super().set_min_detected_photons_filter(value) self._simulator.set_min_detected_photons_filter(value) def set_precision(self, precision: float): self._simulator.set_precision(precision) ================================================ FILE: perceval/simulators/stepper.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from collections import defaultdict import copy from perceval.utils import StateVector, BasicState, BSDistribution, SVDistribution, allstate_iterator from perceval.components import ACircuit from perceval.backends import AStrongSimulationBackend, BACKEND_LIST from .simulator_interface import ISimulator from ._simulator_utils import _to_bsd from ._simulate_detectors import simulate_detectors class Stepper(ISimulator): """ Step-by-step circuit propagation algorithm, main usage is on a circuit, but could work in degraded mode on a list of components [(r, comp)]. """ def __init__(self, backend: AStrongSimulationBackend = None): super().__init__() self._out = None self._backend = backend if backend is None: self._backend = BACKEND_LIST['SLOS']() self._clear_cache() self._C = None def _clear_cache(self): self._result_dict = defaultdict(lambda: {'_set': set()}) self._compiled_input = None def set_circuit(self, circuit: ACircuit, m=None): self._C = circuit self._clear_cache() def apply(self, sv: StateVector, r: list[int], c: ACircuit) -> StateVector: """ Apply a circuit on a StateVector generating another StateVector :param sv: input StateVector :param r: range of port for the circuit corresponding to StateVector position :param c: a circuit :return: evolved StateVector """ min_r = r[0] max_r = r[-1] + 1 key = c.describe() # Can't use c; two identical pieces aren't considered equal if they aren't at the same place # build list of never visited fockstates corresponding to subspace [min_r:max_r] sub_input_state = {sliced_state for state in sv.keys() for sliced_state in (state[min_r:max_r],) if sliced_state not in self._result_dict[key]['_set'] and state[:self._C.m].n >= self.min_detected_photons_filter} # get circuit probability for these input_states if sub_input_state: self._backend.set_circuit(c) mapping_input_output = {} for input_state in sub_input_state: self._backend.set_input_state(input_state) mapping_input_output[input_state] = {output_state: self._backend.prob_amplitude(output_state) for output_state in allstate_iterator(input_state)} self._result_dict[key].update(mapping_input_output) # Union of the dictionaries self._result_dict[key]['_set'] |= sub_input_state # Union of sets # now rebuild the new state vector nsv = StateVector() # May be faster in c++ (impossible to use comprehension here due to successive additions) for state, sv_pa in sv: if state[:self._C.m].n >= self.min_detected_photons_filter: # Useless to compute if the mode will not be selected for output_state, prob_ampli in self._result_dict[key][state[min_r:max_r]].items(): nsv += state.set_slice(slice(min_r, max_r), output_state) * (prob_ampli * sv_pa) else: nsv += state*sv_pa return nsv def probs(self, input_state: BasicState | StateVector) -> BSDistribution: """ Compute the probability distribution from a state input :param input_state: The input fock state or state vector :return: The post-selected output state distribution (BSDistribution) """ return _to_bsd(self.evolve(input_state)) def probs_svd(self, svd: SVDistribution, detectors=None, progress_callback: callable = None) -> dict: """ Compute the probability distribution from a SVDistribution input :param svd: A state vector distribution describing the input to simulate :param detectors: An optional list of detectors :param progress_callback: A function with the signature `func(progress: float, message: str)`. Not used. :return: A dictionary of the form { "results": BSDistribution } * results is the post-selected output state distribution """ res_bsd = BSDistribution() for sv, p_sv in svd.items(): res = self.probs(sv) for bs, p_res in res.items(): res_bsd[bs] += p_res*p_sv if detectors: res_bsd, _ = simulate_detectors(res_bsd, detectors, self.min_detected_photons_filter) return {"results": res_bsd} def evolve(self, input_state: BasicState | StateVector) -> StateVector: """ Evolve a state through the circuit. :param input_state: The input fock state or state vector :return: The output state vector """ self.compile(input_state) assert self._out.m == input_state.m, "Loss channels cannot be used with state amplitude" return self._out def compile(self, input_states: BasicState | StateVector) -> bool: """ Effectively computes the evolution of the input state, and stores the result in self._out. :param input_states: The input fock state or state vector :return: True if something has been computed, False otherwise (i.e. the previous call to compile was already on this circuit and input state, so the result is already stored). """ if isinstance(input_states, BasicState): sv = StateVector(input_states) else: sv = input_states var = [float(p) for _, c in self._C for p in c.get_parameters()] if self._compiled_input == (var, sv): return False self._compiled_input = copy.copy((var, sv)) for r, c in self._C: if hasattr(c, "apply"): sv = c.apply(r, sv) else: sv = self.apply(sv, r, c) self._out = sv return True ================================================ FILE: perceval/utils/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .matrix import Matrix, MatrixN, MatrixS, matrix_double from .format import simple_float, simple_complex, format_parameters from .parameter import Parameter, P, Expression, E from .mlstr import mlstr from .states import BasicState, FockState, NoisyFockState, AnnotatedFockState, Annotation, StateVector, SVDistribution,\ BSDistribution, BSCount, BSSamples, allstate_array, allstate_iterator, anonymize_annotations,\ max_photon_state_iterator, filter_distribution_photon_count from .logical_state import LogicalState, generate_all_logical_states from .polarization import Polarization, convert_polarized_state, build_spatial_output_states from .postselect import PostSelect, post_select_distribution, post_select_statevector from ._random import random_seed from .globals import global_params from .conversion import samples_to_sample_count, samples_to_probs, sample_count_to_samples, sample_count_to_probs,\ probs_to_samples, probs_to_sample_count from .stategenerator import StateGenerator from ._enums import Encoding, InterferometerShape, FileFormat, ModeType from .persistent_data import PersistentData from .versions import PMetadata from .density_matrix import DensityMatrix from .noise_model import NoiseModel from .logging import get_logger, use_perceval_logger, use_python_logger, LoggerConfig, deprecated from .progress_cb import partial_progress_callable from .dist_metrics import tvd_dist, kl_divergence ================================================ FILE: perceval/utils/_enums.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from enum import Enum class Encoding(Enum): """Logical qubit encoding on photons""" DUAL_RAIL = 0 POLARIZATION = 1 RAW = 4 QUDIT2 = 5 QUDIT3 = 6 QUDIT4 = 7 QUDIT5 = 8 QUDIT6 = 9 QUDIT7 = 10 # 2**7 = 128 modes @property def logical_length(self) -> int: """Logical length of an encoding""" n = self.name if n.startswith("QUDIT"): return int(n[len("QUDIT"):]) return 1 @property def fock_length(self) -> int: """Fock state length of an encoding""" if self == Encoding.DUAL_RAIL: return 2 elif self == Encoding.POLARIZATION: return 1 elif self == Encoding.RAW: return 1 return 2**self.logical_length Encoding.DUAL_RAIL.__doc__ = "Dual rail encoding where a qubit is encoded as the position of 1 photon in 2 modes." Encoding.POLARIZATION.__doc__ = "Qubit is encoding on a single photon polarization (horizontal / vertical) in 1 mode." Encoding.RAW.__doc__ = "Raw encoding is the closest to photonics. It encodes a qubit as the presence of photons in 1 mode." Encoding.QUDIT2.__doc__ = ("Qudits are encoding multiple qubits in the location of a single photon in multiple modes. " "QUDIT2 encodes 2 qubits on 1 photon in 4 modes.") Encoding.QUDIT3.__doc__ = "Encodes 3 qubits on 1 photon in 8 modes." Encoding.QUDIT4.__doc__ = "Encodes 4 qubits on 1 photon in 16 modes." Encoding.QUDIT5.__doc__ = "Encodes 5 qubits on 1 photon in 32 modes." Encoding.QUDIT6.__doc__ = "Encodes 6 qubits on 1 photon in 64 modes." Encoding.QUDIT7.__doc__ = "Encodes 7 qubits on 1 photon in 128 modes." class InterferometerShape(Enum): RECTANGLE = 0 TRIANGLE = 1 InterferometerShape.RECTANGLE.__doc__ = ("Rectangular matrix of universal 2-modes components (e.g. MZI). " "All paths have the same depth.") InterferometerShape.TRIANGLE.__doc__ = ("Triangular mesh of universal 2-modes components. " "The top of the interferometer has max depth, the bottom mode has depth 1.") class FileFormat(Enum): BINARY = 0 TEXT = 1 class ModeType(Enum): PHOTONIC = 0 HERALD = 1 CLASSICAL = 2 ModeType.PHOTONIC.__doc__ = "Photonic mode. Additional linear optics components can be added on such a mode." ModeType.HERALD.__doc__ = "Ancillary mode: defines a special photonic mode used for heralding. Nothing can be added." ModeType.CLASSICAL.__doc__ = ("A classical register represents a measured mode. A `Detector` turns a photonic mode into " "a classical bit. Only classical components can be added.") ================================================ FILE: perceval/utils/_random.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import random import numpy as np import exqalibur as xq from .logging import get_logger, channel def random_seed(seed: int = None): """ Initialize the seed used for random number generation (RNG) Impacts the RNG in: * Python core `random` package * numpy * exqalibur .. note:: With a fixed seed, exqalibur RNG is deterministic even inside multithreaded algorithms :param seed: The seed value. If ``None``, use a time-based seed """ random.seed(seed) np.random.seed(seed) if seed is None: get_logger().info("Reset RNG to a time-based random seed", channel.general) xq.set_seed(random.randint(0, 4294967295)) # 0 to 2**32-1 else: get_logger().info(f"Set seed {seed} to RNG", channel.general) xq.set_seed(seed) ================================================ FILE: perceval/utils/_validated_params.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from numbers import Number from abc import abstractmethod, ABC class AValidatedParam(ABC): def __init__(self, default_value = None): if default_value is not None: self._validate(default_value) self.default_value = default_value def __set_name__(self, owner, name): self.private_name = '_' + name self.name = name def __get__(self, obj, objtype=None): value = getattr(obj, self.private_name) if value is None: return self.default_value return value def __set__(self, obj, value): if value is not None: self._validate(value) setattr(obj, self.private_name, value) @abstractmethod def _validate(self, value): pass def is_default(self): return self.default_value is not None class ValidatedBool(AValidatedParam): def __init__(self, default_value=None): super().__init__(default_value) def _validate(self, value): if not isinstance(value, bool): raise TypeError(f"{self.name} expected a boolean value, got {type(value)}") return value class ValidatedFloat(AValidatedParam): def __init__(self, min_value=None, max_value=None, default_value=None): self._min = min_value self._max = max_value super().__init__(default_value) def _validate(self, value): if not isinstance(value, Number): raise TypeError(f"{self.name} expected a numerical value, got {type(value)}") if (self._min is None or self._min <= value) and (self._max is None or value <= self._max): return value raise ValueError(f"{self.name} value out of bound: {value} not in [{self._min}, {self._max}]") ================================================ FILE: perceval/utils/algorithms/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .match import Match import perceval.utils.algorithms.norm as norm ================================================ FILE: perceval/utils/algorithms/circuit_optimizer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from collections.abc import Callable import exqalibur as xq from perceval.components import ACircuit, GenericInterferometer, PS, catalog from perceval.utils import Matrix, P from perceval.utils.logging import get_logger, channel from perceval.serialization import serialize_binary, deserialize_circuit class CircuitOptimizer: """ CircuitOptimizer is a tool designed to set up a circuit with enough variable parameters (i.e. sufficient degrees of freedom) so that it reproduces to the behavior of any other unitary circuit/matrix. Be aware that some circuits are not "universal interferometers" and thus cannot reach every arbitrary unitary matrix. However, the optimizer will get as close as possible. The metric to measure the distance between the target unitary and the optimized interferometer is the matrix fidelity available in `perceval.utils.algorithms.norm`. CircuitOptimizer can be configured with the following parameters: :param threshold: Error threshold = 1-fidelity - i.e. the lower the threshold, the better the output fidelity (default 1e-6) :param ntrials: Number of optimization trials (default 4) :param max_eval_per_trial: maximum number of evaluations per optimization trial (default 200000) """ def __init__(self, threshold: float = 1e-6, ntrials: int = 4, max_eval_per_trial: int = 200000): self.threshold = threshold self.trials = ntrials self.max_eval_per_trial = max_eval_per_trial @property def threshold(self): return self._threshold @threshold.setter def threshold(self, value): if not (0 < value < 1): raise ValueError("Fitness threshold should be between 0 and 1") self._threshold = value @property def trials(self): return self._trials @trials.setter def trials(self, value): self._trials = value @property def max_eval_per_trial(self): return self._max_eval_per_trial @max_eval_per_trial.setter def max_eval_per_trial(self, value): self._max_eval_per_trial = value def optimize(self, target: ACircuit | Matrix, template: ACircuit, empty_mode_list: list[int] = None, first_guess: list[float] = None, ) -> tuple[ACircuit, float]: """ Optimize a template circuit unitary's fidelity with a target matrix or circuit. :param target: The target unitary circuit or matrix :param template: A circuit with variable parameters (supports only beam splitters and phase shifters) :param empty_mode_list: list of the modes without input photon, which are ignored during optimisation as this does not alter the results (default []) :param first_guess: list of starting real value for each parameter. Needs to be the same length as the number of variable parameters in the template circuit (default None, i.e. not using a first guess) :return: A tuple of the best optimized circuit and its fidelity to the target >>> def mzi(i): >>> return Circuit(2) // PS(P(f"phi_1_{i}")) // BS() // PS(P(f"phi_2_{i}")) // BS() >>> def ps(i): >>> return PS(P(f"phi_3_{i}")) >>> template = GenericInterferometer(12, mzi, phase_shifter_fun_gen=ps, phase_at_output=True) >>> random_unitary = Matrix.random_unitary(12) >>> result_circuit, fidelity = CircuitOptimizer().optimize(random_unitary, template) """ if isinstance(target, ACircuit): target = target.compute_unitary() if template.m != target.shape[0]: raise ValueError(f"Template circuit and target size should be the same ({template.m} != {target.shape[0]})") if target.is_symbolic(): raise TypeError("Target must be numeric") if empty_mode_list is None: empty_mode_list = [] optimizer = xq.CircuitOptimizer(target, serialize_binary(template), empty_mode_list) optimizer.set_max_eval_per_trial(self._max_eval_per_trial) optimizer.set_threshold(self._threshold) if first_guess is None: optimized_circuit = deserialize_circuit(optimizer.optimize(self._trials)) else: optimized_circuit = deserialize_circuit(optimizer.optimize(first_guess)) return optimized_circuit, optimizer.fidelity def optimize_rectangle(self, target: Matrix, template_component_generator_func: Callable[[int], ACircuit] = None, phase_at_output: bool = True, allow_error: bool = False, empty_mode_list: list[int] = None ) -> ACircuit: """ Optimize a rectangular circuit to reach a target unitary matrix fidelity. :param target: Target unitary matrix :param template_component_generator_func: Function which generates a base component of the output rectangular circuit (signature(int) -> ACircuit) (default generates a MZI with two variable phases) :param phase_at_output: If True, a layer of phase shifters is added at the output of the circuit. Otherwise, the layer is at the input (default True) :param allow_error: If True, this call will not raise an error when the best fidelity is below threshold Otherwise, raises an error (default False) :param empty_mode_list: list of the modes without input photon, which are ignored during optimisation as this does not alter the results (default []) """ def _gen_ps(i: int): return PS(P(f"phL_{i}")) if template_component_generator_func is None: mzi_name = "mzi phase first" get_logger().debug(f"Using default MZI ({mzi_name}) for rectangular optimization", channel.general) template_component_generator_func = catalog[mzi_name].generate template = GenericInterferometer( target.shape[0], template_component_generator_func, phase_shifter_fun_gen=_gen_ps, phase_at_output=phase_at_output) result_circuit, fidelity = self.optimize(target, template, empty_mode_list) if fidelity < 1 - self._threshold: if allow_error: get_logger().warn(f"Optimization converged with poor fidelity ({fidelity})", channel.general) else: raise RuntimeError(f"Optimization did not converge to expected threshold ({self._threshold})") return result_circuit ================================================ FILE: perceval/utils/algorithms/decomposition.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import copy import numpy as np import sympy as sp import scipy as scp from perceval.utils import Matrix, global_params from .solve import solve def add_phases(phase_shifter_fn, D): phases = [] for idx in range(len(D)): iD = D[idx] a = iD.real b = iD.imag if b != 0 or a < 0: if b == 0: phi = np.pi elif a == 0: if b > 0: phi = np.pi / 2 else: phi = 3 * np.pi / 2 else: phi = np.arctan(b / a) if a < 0: phi = phi + np.pi phases = [(idx, phase_shifter_fn(phi))] + phases return phases def decompose_triangle(u, component, phase_shifter_fn, permutation, precision, constraints, allow_error, ignore_identity_block): m = u.shape[0] params = component.get_parameters() params_symbols = [x.spv for x in params] bounds = [not x.is_periodic and x.bounds or None for x in params] if constraints is None: constraints = [[None] * len(params)] if precision is None: precision = global_params["min_complex_component"] cU = component.U cU_inv = cU.inv() cU_inv.simplify() list_components = [] for j in range(m - 1, 0, -1): for n in range(j): # goal is to null M[n,m] solve_cell = False if abs(u[n, j]) <= precision and ignore_identity_block: solve_cell = True elif permutation is not None: p = [0] for k in range(n + 1, j + 1): p.append(k - n) if abs(u[k, j]) <= precision and ignore_identity_block: p[0] = k - n p[-1] = 0 list_components = [(list(range(n, k + 1)), permutation(p))] + list_components RI = Matrix.eye(m, use_symbolic=False) RI[n, n] = RI[k, k] = 0 RI[k, n] = RI[n, k] = 1 u = RI @ u solve_cell = True break if not solve_cell: equation = cU_inv[0, 0] * u[n, j] + cU_inv[0, 1] * u[n + 1, j] f = sp.lambdify([params_symbols], [equation], modules=[np, scp]) g = lambda *p: np.real(np.abs(f(*p))) x0 = [p.random() for p in params] # look for a constraint solution first res = None for c in constraints: res = solve(g, x0, list(c), bounds, precision, allow_error) if res is not None: break if res is None: return None RI = Matrix.eye(m, use_symbolic=False) instantiated_component = copy.deepcopy(component) substitution = {} for i, r in enumerate(res): substitution[params_symbols[i]] = r instantiated_component.get_parameters()[0].fix_value(res[i]) RI[n, n] = complex(cU_inv[0, 0].subs(substitution)) RI[n, n + 1] = complex(cU_inv[0, 1].subs(substitution)) RI[n + 1, n] = complex(cU_inv[1, 0].subs(substitution)) RI[n + 1, n + 1] = complex(cU_inv[1, 1].subs(substitution)) u = RI @ u list_components = [((n, n + 1), instantiated_component)] + list_components u[n, j] = 0 if phase_shifter_fn: D = np.diag(u) list_components = add_phases(phase_shifter_fn, D) + list_components return list_components def decompose_rectangle(u, component, phase_shifter_fn, permutation, precision, constraints, allow_error, ignore_identity_block): raise NotImplementedError("rectangular decomposition not implemented yet") ================================================ FILE: perceval/utils/algorithms/match.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. class Match: def __init__(self): self._v_map = {} self._pos_map = {} @property def matched(self): return len(self._pos_map) != 0 @property def v_map(self): return self._v_map @property def pos_map(self): return self._pos_map @pos_map.setter def pos_map(self, v): self._pos_map = v def __str__(self): return "pos_map: %s, params: %s" % (self._pos_map, self._v_map) ================================================ FILE: perceval/utils/algorithms/norm.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np from perceval.utils.matrix import Matrix def _count_non_skipped_cols(nb_colums: int, skip_colums: list[int]) -> int: return sum([ 1 for i in range(nb_colums) if i not in skip_colums ]) def fidelity(u: Matrix, v: Matrix, skip_colums: list[int] = []) -> float: r""" Calculate the fidelity of a unitary implementation compared to a reference unitary :param u: the unitary to evaluate :param v: the reference unitary :skip_colums: list of columns (input modes) that should be ignored when computing fidelity :return: real [0-1] float fidelity """ n = _count_non_skipped_cols(u.shape[1], skip_colums) if n == 0: return 1.0 f = abs(frobenius_inner_product(u, v, skip_colums)) ** 2 / (n * frobenius_inner_product(u, u, skip_colums)) if isinstance(f, complex): return f.real else: return f def modulus_fidelity(u: Matrix, v: Matrix, skip_colums: list[int] = []) -> float: r""" Calculate the fidelity of a unitary implementation compared to a reference unitary just comparing single input-single output probabilities :param u: the unitary to evaluate :param v: the reference unitary :skip_colums: list of columns (input modes) that should be ignored when computing fidelity :return: real [0-1] float fidelity """ n = _count_non_skipped_cols(u.shape[1], skip_colums) if n == 0: return 1.0 f = frobenius_inner_product(u, v, skip_colums)/n if isinstance(f, complex): return f.real else: return f def frobenius(u: Matrix, v: Matrix, skip_colums: list[int] = []) -> float: r""" Frobenius norm :param u: the unitary to evaluate :param v: the reference unitary :skip_colums: list of columns (input modes) that should be ignored when computing product :return: real distance between matrices """ difference = u - v return np.sqrt(frobenius_inner_product(difference, difference, skip_colums).real) def frobenius_inner_product(A: np.ndarray, B: np.ndarray, skip_colums: list[int] = []) -> float: # calculates the inner product associated to Frobenius norm C = np.dot(np.transpose(np.conjugate(A)), B) # the generic element of C is C_ij = sum(conj(a_ki).b_kj, k=0..N) # the i-th diagonal element C_ii is the product of i-th column of A and B # C_ii = sum(conj(a_ki).b_ki, k=0..N) # as we want to skip cols from A and B, we omit their indices when computing the trace return sum([C.diagonal()[i] for i in range(C.shape[1]) if i not in skip_colums]) ================================================ FILE: perceval/utils/algorithms/optimize.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from collections.abc import Callable from perceval.components.linear_circuit import ACircuit from perceval.utils import Matrix, P, global_params from scipy import optimize as scpy_optimize def _min_fnc(c: ACircuit, params: list[P], x: list[int], v: Matrix | None, f: Callable[[Matrix, Matrix], float], sign: float): for idx, p in enumerate(x): params[idx].set_value(p) value = f(c.compute_unitary(use_symbolic=False), v) return -sign * value def _stop_criterion(f, f0, precision, accept): if accept: if abs(f-f0) < precision: return True return False def optimize(c: ACircuit, v: Matrix | None, f: Callable[[Matrix, Matrix], float], niter: int = 20, target_opt: float = 0, precision: float = None, n_try: int = 10, sign=1) -> scpy_optimize.OptimizeResult: r"""Optimize parameters of a circuit according to Callable function :param c: circuit with parameters to optimize :param v: the reference unitary, can be None, in such case the fidelity function should ignore the second parameter :param f: fidelity function - first parameter is the unitary of the circuit, second parameter is the reference one :param niter: from `scipy.optimize.basinhopping` :param sign: -1 to find maximal values :param target_opt: optimal value for the function - used with `precision` for early stopping :param precision: used with `target_opt` for early stopping :return: OptimizeResult from scipy library """ if precision is None: precision = global_params["min_complex_component"] params = c.get_parameters() best = None best_x = None while n_try > 0: init_params = [p.random() for p in params] temperature = 1.0 res = scpy_optimize.basinhopping(lambda x: _min_fnc(c, params, x, v, f, sign), init_params, niter=niter, T=temperature, callback=lambda _, f, accept: _stop_criterion(f, target_opt, precision, accept)) if best is None or res.fun < best: best = res.fun best_x = res.x if _stop_criterion(res.fun, target_opt, precision, True): break n_try -= 1 temperature *= 1.1 res.fun = best * -sign res.x = best_x return res ================================================ FILE: perceval/utils/algorithms/simplification.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np import perceval.components.unitary_components as comp from perceval.components.linear_circuit import ACircuit, Circuit from perceval.utils.logging import get_logger, channel def simplify(circuit: list | ACircuit, m: int = None, display: bool = False) -> list | Circuit: r""" Simplify a circuit when possible :param circuit: the circuit to simplify. This input can be: * a unitary ``Circuit`` * a list of positioned components. These components do not have to be unitary (e.g. [((0,1), BS()), ((0,), PS(phi=0.5)), ((1,), TD(1))]) :param m: the circuit size (in modes), required only when ``circuit`` is a list. :param display: Slightly changes the behaviour of the function, give a circuit which is displayed more similarly than the input. Use ``False`` when computing. Default is ``False``. :return: The simplified circuit (same type as the input) """ final_circuit_comp = [] if isinstance(circuit, ACircuit): m = circuit.m else: assert m is not None, "m must be specified" for r, c in circuit: if isinstance(r, int): r = tuple(r + i for i in range(c.m)) final_circuit_comp.append([r, c]) final_circuit_comp = _simplify_comp(final_circuit_comp, m, display) if isinstance(circuit, Circuit): res = Circuit(m) for r, c in final_circuit_comp: res.add(r, c) return res return final_circuit_comp def _simplify_comp(components, m: int=None, display: bool=False): # Simplify the circuit according to the last added component [_, c] = components[-1] if isinstance(c, comp.PERM): return _simplify_perm(components, m, display) if isinstance(c, comp.PS): return _simplify_PS(components, m, display) else: return components # Permutation simplifications ################################################################### # These functions act directly on permutation list; could be useful outside the simplification def extend_perm(r, perm_list, m): M_r = r[-1] + 1 new_perm = list(range(r[0])) + [perm_list[i] + r[0] for i in range(len(perm_list))] + list(range(M_r, m)) return list(range(m)), new_perm def perm_compose(left_r, left_perm, right_r, right_perm): max_r = max(left_r[-1] + 1, right_r[-1] + 1) # Resize the perm lists, so they begin at mode 0 and end at the same mode new_r, left_perm = extend_perm(left_r, left_perm, max_r) right_perm = extend_perm(right_r, right_perm, max_r)[1] # Now compose the perms new_perm = [right_perm[left_perm[i]] for i in range(len(right_perm))] return list(range(max_r)), new_perm def reduce_perm(r, perm): n = len(perm) for i in range(n): if perm[i] != i: break for j in range(n - 1, -1, -1): if perm[j] != j: break perm = [perm[k] - i for k in range(i, j + 1)] return r[i: j + 1], perm def invert_permutation(permutation): inv = np.empty_like(permutation) inv[permutation] = np.arange(len(inv), dtype=inv.dtype) # Conversion to python list of integers, necessary for comp.PERM creation return [inv[i].item() for i in range(len(inv))] ################################################################################################### def _update_adjacent(adjacent_modes, r): min_r = r[0] i = 0 while i < len(adjacent_modes): # Needed as the size of adjacent_modes will change modes = adjacent_modes[i] if min_r in modes: adjacent_modes[i] = list(set(modes).union(set(r))) else: for mode in modes: if mode in r: adjacent_modes.pop(i) i -= 1 break i += 1 def _generate_compatible_perm(perm_list, adjacent_modes): # Assumes the perm_list corresponds to the permutation at the right m = len(perm_list) reverse = [-1] * m first_step = [] second_step = [] third_step = [] for modes in adjacent_modes: if len(modes) > 1: m_mode = modes[0] M_mode = modes[-1] out = [perm_list[mode] for mode in modes] min_out = min(out) max_out = max(out) if max_out - min_out == M_mode - m_mode: # The modes are kept adjacent by the permutation, keep it for the second phase second_step.append(modes) else: # Some modes diverge first_step.append(modes) else: third_step.append(modes[0]) first_step.sort(key=lambda modes: min(perm_list[modes[i]] for i in range(len(modes)))) second_step.sort(key=lambda modes: min(perm_list[modes[i]] for i in range(len(modes)))) third_step.sort(key=lambda mode: perm_list[mode]) third_step.reverse() # Visit the lonely modes in the reverse order to maximize the number of crosses for step in (first_step, second_step): for modes in step: out = [perm_list[mode] for mode in modes] reverse = _update_perm(reverse, min(out), modes) reverse2 = reverse.copy() # Now fill the holes with single modes (adjacent_modes is ordered so modes will not be too much moved) for mode in third_step: reverse2 = _update_perm(reverse2, perm_list[mode], [mode]) # Check if no permutation occurs, it's always worth to try something if reverse2 == list(range(m)): # We try to shuffle the lonely modes third_step.reverse() for mode in third_step: reverse = _update_perm(reverse, perm_list[mode], [mode]) else: reverse = reverse2 # Now, we have to find the permutation such that the created permutation with this one is perm_list right_perm = perm_compose(tuple(range(m)), reverse, tuple(range(m)), perm_list)[1] # return left_perm, right_perm return reverse, invert_permutation(right_perm) def _search_empty_space(perm, n, init): # Find the nearest non-attributed space of len n in the permutation target = n * [-1] for i in range(len(perm)): if i + init + n <= len(perm) and perm[init + i: init + i + n] == target: return i, n if init - i >= 0 and perm[init - i: init - i + n] == target: return -i, n # If there is no available space, seek for a smaller space; perm will be changed return _search_empty_space(perm, n - 1, init) def _update_perm(perm, init, modes): m = len(perm) i, n = _search_empty_space(perm, len(modes), init) slice_min = init + i slice_max = init + i + n j_right = 0 j_left = 1 while len(modes) - n: # There is not enough space, some modes must be moved cur_index = slice_max + j_right if cur_index < m and perm[cur_index] == -1: perm[slice_max + 1: cur_index + 1] = perm[slice_max: cur_index] perm[slice_max] = -1 slice_max += 1 n += 1 else: j_right += 1 if not len(modes) - n: break cur_index = slice_min - j_left if cur_index >= 0 and perm[cur_index] == -1: perm[cur_index: slice_min - 1] = perm[cur_index + 1: slice_min] perm[slice_min - 1] = -1 slice_min -= 1 n += 1 else: j_left += 1 perm[slice_min: slice_max] = modes return perm def _move_comp(in_components, perm): # Perm is the inverse of the right left permutation new_in_comp = [] for r, c in in_components: mode = perm[r[0]] new_r = [mode + i for i in range(len(r))] new_in_comp.append([new_r, c]) return new_in_comp def _evaluate_perm(left_perm_list, right_perm_list, display): if display: # Useful in case of display s = 0 for i in range(len(left_perm_list)): if i != left_perm_list[i]: s += abs(left_perm_list[i] - i) s += 1 return s + len(left_perm_list) + len(right_perm_list) else: # Useful in term of computation for the Stepper return len(left_perm_list) + len(right_perm_list) def _simplify_perm(components, m: int = None, display: bool = False): [r, c] = components.pop() end_components = components # Check several permutations found_other_perm = False get_logger().debug(f"Enter PERM simplification for component {c.perm_vector}", channel.general) for i in range(len(components) - 1, -1, -1): [previous_r, previous_c] = components[i] if isinstance(previous_c, comp.PERM): found_other_perm = True adjacent_modes = [[j] for j in range(m)] in_components = [] for j in range(i + 1, len(components)): mid_r, mid_c = components[j] _update_adjacent(adjacent_modes, mid_r) in_components.append([mid_r, mid_c]) break if found_other_perm and i == len(components) - 1: # The permutations are successive [left_r, left_c] = end_components.pop(-1) left_perm = left_c.perm_vector perm = c.perm_vector new_r, new_c_perm = perm_compose(left_r, left_perm, r, perm) new_r, new_c_perm = reduce_perm(new_r, new_c_perm) get_logger().debug(f" Simplifying {perm} with a successive PERM component of perm vector " f"= {left_perm}", channel.general) if len(new_r): end_components.append([new_r, comp.PERM(new_c_perm)]) elif found_other_perm and len(adjacent_modes) > 1: # Non-successive permutations and things to do get_logger().debug(" Simplifying with non-successive permutation components", channel.general) # Simulates an unraveling on a smaller circuit with only the permutations # First, we extend our permutations to the entire circuit extended_r, c_list = extend_perm(r, c.perm_vector, m) previous_c_list = extend_perm(previous_r, previous_c.perm_vector, m)[1] # Then we generate permutations that are compatible with our dependent modes left_right_perm, left_left_perm = _generate_compatible_perm(invert_permutation(previous_c_list), adjacent_modes) # Now we can unravel the middle compatible permutation right_perm = perm_compose(extended_r, left_right_perm, extended_r, c_list)[1] right_r, right_perm = reduce_perm(extended_r, right_perm) # Score evaluation left_r, left_perm = reduce_perm(extended_r, left_left_perm) old_score = _evaluate_perm(reduce_perm(extended_r, previous_c_list)[1], reduce_perm(extended_r, c_list)[1], display) new_score = _evaluate_perm(left_perm, right_perm, display) if old_score > new_score: # We now have to move the components new_in_comp = _move_comp(in_components, invert_permutation(left_right_perm)) end_components = components[:i] if len(left_r): end_components.append([left_r, comp.PERM(left_perm)]) end_components += new_in_comp if len(right_r): end_components.append([right_r, comp.PERM(right_perm)]) else: r, c = reduce_perm(extended_r, c_list) if len(r): end_components.append([r, comp.PERM(c)]) else: # It's the only permutation or it can't be simplified with the previous one r, c = reduce_perm(r, c.perm_vector) if len(r): end_components.append([r, comp.PERM(c)]) return end_components # Phase shifter simplification def _simplify_PS(components, m: int = None, display: bool = False): PS_found_n_simplified = False [_, c] = components[-1] phi = c.param("phi") if not phi.is_variable: # encountered a PS with numeric value of phase - simplifying get_logger().debug(f"Enter simplification of a PS component with parameter {phi}", channel.general) [r, c] = components.pop() r0 = r[0] for i in range(len(components) - 1, -1, -1): [previous_r, previous_c] = components[i] if isinstance(previous_c, comp.PS) and r0 == previous_r[0]: previous_phi = previous_c.param("phi") if not previous_phi.is_variable: get_logger().debug(" Simplify as two consecutive PS with numerical phase values found", channel.general) PS_found_n_simplified = True # previous PS does not have a variable phi -> simplify both together new_phi = float(phi) + float(previous_phi) if new_phi % (2 * np.pi) != 0 or display: new_c = comp.PS(new_phi) components[i] = [previous_r, new_c] else: # simplification summed to phase=0 components.pop(i) break elif isinstance(previous_c, comp.PERM): perm_list = extend_perm(previous_r, previous_c.perm_vector, m)[1] r0 = invert_permutation(perm_list)[r0] elif r0 in previous_r: # a component other than PS or PERM in the same mode as current PS break if not PS_found_n_simplified and (float(phi) % (2 * np.pi) or display): get_logger().debug(" Re-including the non-simplified PS if it either doesn't have a phase equal to " "multiple of 2*pi or needs to be displayed", channel.general) components.append([r, c]) return components ================================================ FILE: perceval/utils/algorithms/solve.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import scipy.optimize as so def solve(f, x0, constraint, bounds, precision, allow_error=False): r"""Solve f starting with x0 and compliant with constraints. :param allow_error: :param bounds: :param f: :param x0: :param constraint: :param precision: :return: """ if len(x0) == 0: if abs(f([])) < precision: return [] for i, c in enumerate(constraint): if c is not None: c = float(c) res = solve(lambda x: f([*x[:i], c, *x[i:]]), x0[:i]+x0[i+1:], constraint[:i]+constraint[i+1:], bounds[:i]+bounds[i+1:], precision, allow_error) if res is None: return None return [*res[:i], c, *res[i:]] if x0: res = so.minimize(f, x0, method="L-BFGS-B", bounds=[b is not None and (float(b[0]), float(b[1])) or (None, None) for b in bounds]) f_x = res.fun x = res.x else: f_x = f([]) x = [] if f_x > precision and not allow_error: return None return x ================================================ FILE: perceval/utils/conversion.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from collections import Counter import random import numpy as np from .states import BSDistribution, BSCount, BSSamples def _deduce_count(**kwargs) -> int: count = kwargs.get("count") if count is not None: return count max_shots = kwargs.get("max_shots") max_samples = kwargs.get("max_samples") if max_shots is not None and max_samples is not None: return min(max_samples, max_shots) # Not accurate in terms of shot limit count = max_shots or max_samples if count is None: raise RuntimeError("kwargs does not contain sample count information") return count # Conversion functions (samples <=> probs <=> sample_count) def samples_to_sample_count(sample_list: BSSamples) -> BSCount: """ Convert a chronological measured sample list to a state count :param sample_list: the list to convert :return: the state count """ return BSCount(Counter(sample_list)) def samples_to_probs(sample_list: BSSamples) -> BSDistribution: """ Convert a chronological measured sample list to a state distribution :param sample_list: the list to convert :return: the state distribution """ return sample_count_to_probs(samples_to_sample_count(sample_list)) def probs_to_sample_count(probs: BSDistribution, **kwargs) -> BSCount: """ Convert a measured state probability distribution to a state count. This conversion artificially adds random sampling noise, following a normal law, to the result. :param probs: the distribution to convert :keyword count: (``int``) -- The final number of samples to generate. Can be None if either of the remaining kwargs is defined. :keyword max_shots: (``int``) -- If both ``max_shots`` and ``max_samples`` are given, then the minimum of the two will be used. Else, the one defined will be used if ``count`` is not given. :keyword max_samples: (``int``) -- See ``max_shots``. :return: the state count """ count = _deduce_count(**kwargs) if count < 1: return BSCount() perturbed_dist = {state: max(prob + np.random.normal(scale=(prob * (1 - prob) / count) ** .5), 0) for state, prob in probs.items()} prob_sum = sum(perturbed_dist.values()) if prob_sum == 0: return samples_to_sample_count(probs_to_samples(probs, count=count)) fac = 1 / prob_sum perturbed_dist = {key: fac * prob for key, prob in perturbed_dist.items()} # Renormalisation if max(perturbed_dist.values()) * count < 1: return samples_to_sample_count(probs_to_samples(probs, count=count)) results = BSCount() for state in perturbed_dist: results.add(state, round(perturbed_dist[state] * count)) # Artificially deal with the rounding errors diff = round(count - sum(results.values())) if diff > 0: results[random.choice(list(results.keys()))] += diff elif diff < 0: while diff < 0: k = random.choice(list(results.keys())) current_diff = max(-results[k], diff) diff -= current_diff results[k] += current_diff return results def probs_to_samples(probs: BSDistribution, **kwargs) -> BSSamples: """ Convert a measured state probability distribution to a chronological list of samples :param probs: the distribution to convert :keyword count: (``int``) -- The final number of samples to generate. Can be None if either of the remaining kwargs is defined. :keyword max_shots: (``int``) -- If both ``max_shots`` and ``max_samples`` are given, then the minimum of the two will be used. Else, the one defined will be used if ``count`` is not given. :keyword max_samples: (``int``) -- See ``max_shots``. :return: the sample list """ count = _deduce_count(**kwargs) return probs.sample(count, non_null=False) def sample_count_to_probs(sample_count: BSCount) -> BSDistribution: """ Convert a state count to a state probability distribution :param sample_count: the state count :return: the state probability distribution """ bsd = BSDistribution() for state, count in sample_count.items(): if count == 0: continue if count < 0: raise RuntimeError(f"A sample count must be positive (got {count})") bsd[state] = count if len(bsd): bsd.normalize() return bsd def sample_count_to_samples(sample_count: BSCount, **kwargs) -> BSSamples: """ Convert a state count to a chronological list of samples, by randomly sampling on the count :param sample_count: the state count :keyword count: (``int``) -- The final number of samples to generate. Can be None to deduce it from the number of samples in the BSCount. :keyword max_shots: (``int``) -- If both ``max_shots`` and ``max_samples`` are given, then the minimum of the two will be used. Else, the one defined will be used if ``count`` is not given. :keyword max_samples: (``int``) -- See ``max_shots``. :return: the sample list """ try: count = _deduce_count(**kwargs) except RuntimeError: count = sum(sample_count.values()) return sample_count_to_probs(sample_count).sample(count, non_null=False) ================================================ FILE: perceval/utils/density_matrix.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import random from copy import copy from math import comb, sqrt import numpy as np from numpy import conj from scipy.linalg import eigh from scipy.sparse.linalg import LinearOperator, eigsh from scipy.sparse import dok_array, csr_array, kron import exqalibur as xq from .states import StateVector, SVDistribution, BasicState, max_photon_state_iterator, BSSamples, FockState from .density_matrix_utils import array_to_statevector, is_hermitian, sparray # In all the DensityMatrix Class, there is a compromise between csr_array and dok_array. # The first one is well suited for matrix-vector product, the other one is easier to construct from scratch SPARSE_THRESHOLD = 50 class FockBasis(dict): def __init__(self, m, n_max): super().__init__() for i, st in enumerate(max_photon_state_iterator(m, n_max)): self[st] = i self._m = m self._n_max = n_max def add_photon(self): self._n_max += 1 length = len(self) new_states = xq.FSArray(self._m, self._n_max) for i, st in enumerate(new_states): self[st] = length+i def add_photons(self, n): for _ in range(n): self.add_photon() @property def m(self): return self._m @property def n_max(self): return self._n_max def density_matrix_tensor_product(A, B): """ Make the tensor product of 2 Density Matrices \ :param A, B: two density matrices \ :return: the "kronecker" product of the density matrices, in the correct basis """ if not isinstance(A, DensityMatrix): A = DensityMatrix.from_svd(A) if not isinstance(B, DensityMatrix): B = DensityMatrix.from_svd(B) n_max = A.n_max + B.n_max n_mode = A.m + B.m size = comb(n_max+n_mode, n_max) new_index = FockBasis(n_mode, n_max) matrix = kron(A.mat, B.mat) perm = dok_array((A.size*B.size, size), dtype=complex) # matrix from tensor space to complete Fock space for i, a_state in enumerate(A.inverse_index): for j, b_state in enumerate(B.inverse_index): index = new_index[a_state*b_state] perm[i*B.size+j, index] = 1 matrix = perm.T @ matrix @ perm return DensityMatrix(matrix, new_index, check_hermitian=False) class DensityMatrix: """ Density operator representing a mixed state. Does not support annotations yet. :param mixed_state: 2d-array, SVDistribution, StateVector or Basic State representing a mixed state :param index: index of all BasicStates accessible from this mixed states through a unitary evolution :param m: optional number of modes if index is not given :param n_max: optional maximum number of photon if index is not given """ def __init__(self, mixed_state: np.ndarray | sparray, index: FockBasis | None = None, m: int | None = None, n_max: int | None = None, check_hermitian: bool = True, precision: bool = 1e-6): """ Constructor for the DensityMatrix Class """ # Here the constructor for a matrix if not isinstance(mixed_state, (np.ndarray, sparray)): raise TypeError(f"Can't construct a density matrix from {type(mixed_state)}") if check_hermitian: if not is_hermitian(mixed_state): raise AssertionError("A density Matrix must be Hermitian") if index is None: if not (m is None or n_max is None): index = FockBasis(m, n_max) else: raise ValueError("you must provide an index or a number of modes and photons") if not isinstance(index, FockBasis): raise ValueError(f"index must be a FockBasis object. {type(index)} was given") if len(index) != mixed_state.shape[0]: raise ValueError(f"The index length is incompatible with your matrix size. \n " f"For at most {index.n_max} photons in {index.m} modes, your matrix size must be {len(index)}") self.mat = csr_array(mixed_state, dtype=complex) self._size = self.mat.shape[0] self._m = index.m self._n_max = index.n_max self.precision = precision self.index = dict() self.inverse_index = [] self.set_index(index) # index construction @staticmethod def from_svd(svd: SVDistribution | StateVector | BasicState, index: FockBasis | None = None): """ Construct a Density matrix from a SVDistribution. :param svd: an SVDistribution object representing the mixed state \ :param index: the basis in which the density matrix is expressed. Self generated if incorrect \ :return: the DensityMatrix object corresponding to the SVDistribution given \ """ if isinstance(svd, (StateVector, BasicState)): svd = SVDistribution(svd) if not isinstance(svd, SVDistribution): raise TypeError("mixed_state must be a BasicState, a StateVector a SVDistribution or a 2d array") for key in svd.keys(): if any([not isinstance(bs[0], FockState) for bs in key]): raise ValueError("annotations are not supported yet in DensityMatrix") m = svd.m n_max = svd.n_max size = comb(m+n_max, m) if not(isinstance(index, FockBasis) and index.m == m and index.n_max >= n_max): index = FockBasis(m, n_max) l = [] for sv, p in svd.items(): vector = np.zeros((size, 1), dtype=complex) for bst, pa in sv: idx = index[bst] vector[idx, 0] = pa vector = csr_array(vector) l.append((vector, p)) matrix = sum([p * (vector @ conj(vector.T)) for vector, p in l]) # This avoids the SparseEfficiencyWarning return DensityMatrix(matrix, index, check_hermitian=False) def set_index(self, index: dict): if index is None: k = 0 for key in max_photon_state_iterator(self._m, self._n_max): self.index[key] = k self.inverse_index.append(key) k += 1 else: if len(index) == self._size: self.index = index self.inverse_index = [None]*self._size for key in index.keys(): self.inverse_index[index[key]] = key else: raise ValueError("the index size does not match the matrix size") def __getitem__(self, key: tuple[BasicState, BasicState]): """key must be a BasicState tuple""" key1, key2 = key if not isinstance(key1, BasicState) or not isinstance(key2, BasicState): raise TypeError("Expected BasicState tuple") i, j = self.index[key1], self.index[key2] return self.mat[i, j] @staticmethod def _deflation(A: sparray, val: np.ndarray, vec: np.ndarray): """ Defines the mat_vec function of the Linear operator after the deflation of all the vectors in the vec array. :param A: any kind of sparse matrix :param val: the array of eigen_values :param vec: the array of eigen_vector """ if val.shape[0] != vec.shape[1]: raise ValueError("inconsistent number of eigenvectors and eigenvalues") if vec.shape[0] != A.shape[0]: raise ValueError("the size of the matrix is inconsistent with this of the eigenvector") def matrix_vector_multiplication(x: np.ndarray): """ The matrix vector multiplication function associated to the deflated operator """ result = A @ x for k in range(val.shape[0]): result -= val[k] * (conj(vec[:, k].T) @ x) * vec[:, k] return result return matrix_vector_multiplication @staticmethod def _bra_str(bs: BasicState): return "<" + str(bs)[1:][:-1] + "|" def _to_svd_small(self, threshold): """Extracting the svd using the scipy method on arrays""" matrix = self.mat.toarray() w, v = eigh(matrix) dic = {} for k in range(w.shape[0]): sv = array_to_statevector(v[:, k], self.inverse_index) if w[k] > threshold: dic[sv] = w[k] return SVDistribution(dic) def _to_svd_large(self, threshold, batch_size): """Extracting the svd using the scipy method on sparse matrices""" val = np.array([]) vec = np.empty(shape=(self._size, 0), dtype=complex) while (val > threshold).all(): deflated_operator = LinearOperator((self._size, self._size), matvec=self._deflation(self.mat, val, vec)) new_val, new_vec = eigsh(deflated_operator, batch_size) val = np.concatenate((val, new_val)) vec = np.concatenate((vec, new_vec), axis=1) dic = {} for i in range(val.shape[0]): if val[i] >= threshold: sv = StateVector() for j in range(len(vec)): sv += complex(vec[j][i]) * self.inverse_index[j] sv.normalize() if sv.m != 0: dic[sv] = val[i] else: continue return SVDistribution(dic) def to_svd(self, threshold: float | None = None, batch_size: int = 1): """ Gives back an SVDistribution from the density_matrix :param threshold: the threshold when the search for eigen values is stopped. :param batch_size: the number of eigen values at each Arnoldi's algorithm iteration. Only used if matrix is large enough. :return: The SVD object corresponding to the DensityMatrix. The StateVector with probability < threshold are removed. """ if threshold is None: threshold = self.precision if self.size < SPARSE_THRESHOLD: # if the matrix is small: array eigh method return self._to_svd_small(threshold) else: # if the matrix is large: sparse eigsh method return self._to_svd_large(threshold, batch_size) def __radd__(self, other): """ Method used to be compatible with the sum build-in function of python Exists ONLY for this case ONLY!!! """ if other == 0: return self else: raise TypeError("You can only add a Density Matrix to a Density Matrix") def __add__(self, other): """ add two density Matrices together """ if not isinstance(other, DensityMatrix): raise TypeError("You can only add a Density Matrix to a Density Matrix") if self._m != other._m: raise ValueError("You can't add Density Matrices with different numbers of mode") n = max(self._n_max, other.n_max) if n == self.n_max: small_matrix = other big_matrix = self else: small_matrix = self big_matrix = other copy_mat = copy(small_matrix.mat) copy_mat.resize(big_matrix.size, big_matrix.size) new_mat = copy_mat + big_matrix.mat new_index = big_matrix.index return DensityMatrix(new_mat, new_index, check_hermitian=False) def __mul__(self, other): """ Make a tensor product between a Density Matrix and a mixed state in any form Or make a scalar multiplication """ if isinstance(other, (int, float, complex)): new_dm = copy(self) new_dm.mat = other*new_dm.mat return new_dm return density_matrix_tensor_product(self, other) def __rmul__(self, other): """ Make a tensor product between a mixed in any form and a Density Matrix Or make a scalar multiplication """ if isinstance(other, (int, float, complex)): new_dm = copy(self) new_dm.mat = other*new_dm.mat return new_dm else: return density_matrix_tensor_product(other, self) def remove_low_amplitude(self, threshold: float | None = None): """ Remove the lines and column where the amplitude is below a certain threshold """ if threshold is None: threshold = self.precision projector = dok_array(self.shape) for k in range(self.size): if self.mat[k, k] > threshold: projector[k, k] = 1 projector = csr_array(projector) self.mat = projector.dot(self.mat).dot(projector) self.normalize() def normalize(self): """ Normalize the density matrix so that Trace(rho) = 1 """ factor = self.mat.trace() if abs(factor-1) >= self.precision: self.mat = (1/factor)*self.mat def sample(self, count: int = 1) -> BSSamples: """ Sample a basic state on the density matrix """ self.normalize() samples = random.choices(self.inverse_index, list(self.mat.diagonal().real), k=count) output = BSSamples() for state in samples: output.append(state) return output def measure(self, modes: list[int] | int): """ Makes a measure on a list of modes. :param modes: a list of integer for the modes you want to measure """ self.normalize() if isinstance(modes, int): modes = [modes] projectors = self._construct_all_projectors(modes) res = dict() # result fo the form {measured FockState: (remaining density matrix, probability) for key_fs, item_list in projectors.items(): basis = item_list[0] # FockBasis of possible measurement projector = item_list[1] prob = item_list[2] if prob != 0: collapsed_dm = projector @ self.mat @ projector.T # wave function collapse resulting_dm = DensityMatrix(collapsed_dm, basis, check_hermitian=False) resulting_dm.normalize() res[key_fs] = (prob, resulting_dm) return res def _construct_projector_one_sample(self, modes, fock_state) -> tuple[FockBasis, dok_array]: """ Construct the projection operator onto the subspace of some number photons on some mode """ if len(modes) != fock_state.m: raise ValueError(f"you can't have {fock_state} in {len(modes)} number of modes") basis = FockBasis(self.m-fock_state.m, self.n_max-fock_state.n) projector = dok_array((len(basis), self.size), dtype=float) for i, fs in enumerate(self.inverse_index): meas_fs, remain_fs = self._divide_fock_state(fs, modes) if meas_fs == fock_state: projector[basis[remain_fs], i] = 1 return basis, projector def _construct_all_projectors(self, modes: list[int]) -> dict: """ construct all the projectors associated with some modes :return: a dictionary with for each measured state a list [fock_basis, projector, probability] """ modes = list(set(modes)) res = dict() for nb_measured_photons in range(self.n_max+1): # FockBasis for the remaining density matrices remaining_basis = FockBasis(self.m - len(modes), self.n_max - nb_measured_photons) for measured_fs in xq.FSArray(len(modes), nb_measured_photons): # initialisation of the empty projectors res[measured_fs] = [remaining_basis, dok_array((len(remaining_basis), self.size)), 0] diag_coefs = self.mat.diagonal() for i, fs in enumerate(self.inverse_index): # construction of the projectors prob = abs(diag_coefs[i]) measured_fs, remaining_fs = self._divide_fock_state(fs, modes) remaining_basis = res[measured_fs][0] new_basis_idx = remaining_basis[remaining_fs] res[measured_fs][1][new_basis_idx, i] = 1 res[measured_fs][2] += prob return res @staticmethod def _divide_fock_state(fs, modes): """ divide a BasicState into two BasicStates """ measured_fs = [] remaining_fs = [] for mode in range(fs.m): if mode in modes: measured_fs.append(fs[mode]) else: remaining_fs.append(fs[mode]) return BasicState(measured_fs), BasicState(remaining_fs) @staticmethod def _get_annihilated_fockstate(fockstate, m, n_photon): """ give the fockstate after loss of n_photon in the mode m """ listed_fs = list(fockstate) if listed_fs[m] <= n_photon: listed_fs[m] = 0 else: listed_fs[m] -= n_photon return BasicState(listed_fs) def _construct_loss_operators(self, mode: int, p: float): """ Construct the kraus operators for a loss channel on specified modes """ operators = [dok_array(self.shape, dtype=float) for _ in range(self._n_max+1)] # We separate the states depending on the number of lost photons # Because there is no more coherence between those states for n_photon_loss in range(len(operators)): for state, idx in self.index.items(): n_photon = state[mode] if n_photon >= n_photon_loss: result_idx = self.index[self._get_annihilated_fockstate(state, mode, n_photon_loss)] # Binomial probability, (comes from the simplification of the beam splitter action) operators[n_photon_loss][result_idx, idx] += sqrt(comb(n_photon, n_photon_loss) * (1-p)**(n_photon-n_photon_loss) * p**n_photon_loss) return operators def apply_loss(self, modes: int | list, prob: float): """ Apply a loss on some mode according to some probability of losing a photon Everything works like if the mode was connected to some virtual mode with a beam splitter of reflectivity prob :param modes: the mode were you want to simulate a loss :param prob: the probability to lose a photon """ if isinstance(modes, int): modes = [modes] for mode in modes: self._apply_loss(mode, prob) def _apply_loss(self, mode: int, prob: float): matrix_after_loss = csr_array(self.shape, dtype=complex) for operator in self._construct_loss_operators(mode, prob): matrix_after_loss += operator @ self.mat @ operator.T self.mat = matrix_after_loss def __str__(self): """ string representation of a density matrix """ string = "" for i in range(self._size): for j in range(self._size): if self.mat[i, j] == 0: continue else: new_term = (f"{self.mat[i, j]:.2f}*" + str(self.inverse_index[j]) + self._bra_str(self.inverse_index[i]) + "+") string += new_term return string[:-1] def __repr__(self): return str(self.mat.toarray()) @property def n_max(self): return self._n_max @property def m(self): return self._m @property def shape(self): return self._size, self._size @property def size(self): return self._size ================================================ FILE: perceval/utils/density_matrix_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np from scipy.sparse import csr_array from scipy.sparse import sparray from perceval.utils.states import StateVector def extract_upper_triangle(csr_matrix: csr_array) -> csr_array: """ extract sort of the upper triangle of the matrix if the input matrix M is hermitian, we have S + S_dagger = M with M the output matrix """ result_data = [] result_indices = [] result_indptr = [0] size = csr_matrix.shape[0] for row in range(size): start_idx = csr_matrix.indptr[row] end_idx = csr_matrix.indptr[row + 1] for i in range(start_idx, end_idx): col = csr_matrix.indices[i] # Only include elements in the upper triangle if col > row: result_data.append(csr_matrix.data[i]) result_indices.append(col) elif col == row: result_data.append(csr_matrix.data[i]/2) result_indices.append(col) result_indptr.append(len(result_data)) # Create a new CSR matrix using the extracted upper triangular part upper_triangle_matrix = csr_array((result_data, result_indices, result_indptr), shape=csr_matrix.shape) return upper_triangle_matrix def statevector_to_array(sv: StateVector, index: dict): """ translate a StateVector object into an array :param sv: a StateVector :param index: a dictionary with BasicStates as keys and indices as values """ vector = np.zeros(len(index), dtype=complex) for key, value in sv: vector[index[key]] += value return vector def array_to_statevector(vector: np.ndarray | sparray, reverse_index: list): """ translate an array in a StateVector :param vector: an array :param reverse_index: a list of BasicStates, describing a mapping from indices to the corresponding basic states """ sv = StateVector() for i, x in enumerate(reverse_index): if vector[i] == 0: continue else: sv += complex(vector[i])*x return sv def is_hermitian(matrix: sparray | np.ndarray) -> bool: n, m = matrix.shape if n == m: for i in range(n): for j in range(i, n): if not np.isclose(matrix[i, j], matrix[j, i]): return False return True return False ================================================ FILE: perceval/utils/dist_metrics.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.utils import BSDistribution from perceval.utils.logging import get_logger, channel from math import log def tvd_dist(dist_lh: BSDistribution, dist_rh: BSDistribution) -> float: """ Computes the Total Variation Distance (TVD) between two input BSDistributions. :param dist_lh: First BSDistribution :param dist_rh: Second BSDistribution :return: total variation distance between the two BSDistributions (value between 0 and 1) """ only_dist_lh_states = set(dist_lh.keys()) - set(dist_rh.keys()) only_dist_rh_states = set(dist_rh.keys()) - set(dist_lh.keys()) if only_dist_rh_states or only_dist_lh_states: get_logger().warn("Some Basic states are missing in one or both of the two input distributions. " "Their values will be set to 0 before computing TVD.", channel.user) all_states = set(dist_lh.keys()).union(dist_rh.keys()) tvd = 0.5 * sum(abs(dist_lh.get(basic_state, 0) - dist_rh.get(basic_state, 0)) for basic_state in all_states) return tvd def kl_divergence(ideal_dist: BSDistribution, est_dist: BSDistribution) -> float: """ Computes the Kullback-Leibler (KL) divergence of a model (simulated/observed) BSDistribution with respect to an ideal BSDistribution. Our computation ignores states absent from the estimated distribution or have null probabilities. :param ideal_dist: Ideal BSDistribution (known from theory or an ideal computation) :param est_dist: Estimated BSDistribution (simulated or observed from experiment) :return: KL divergence of the estimated distribution relative to the ideal. """ kl_div = 0 zero_states_count = 0 # states with null probabilities or missing in estimated distribution for state, ideal_prob in ideal_dist.items(): est_prob = est_dist.get(state, 0) if est_prob > 0: kl_div += ideal_prob * log(ideal_prob/est_prob) else: zero_states_count += 1 if zero_states_count > 0: get_logger().warn(f"{zero_states_count} Basic states are absent from the " f"estimated BSDistribution with respect to the ideal. These states are" f" excluded from KL divergence computation.", channel.user) return kl_div ================================================ FILE: perceval/utils/format.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sympy as sp import numpy as np def simple_float(alpha, precision=1e-6, nsimplify=True, fracmax=63, multiplier=1, mult10=None): r""" try to get simple formula for remarkable numbers simple fraction, simple fraction, simple pi fraction, or fraction + fraction*remarkable root sqrt(2)/sqrt(5) return sympy object, str object """ sign = 1 if alpha < 0: sign = -1 alpha = -alpha # look for n/p*pi or n/p if nsimplify: for r in range(1, fracmax): for multiplier2 in [sp.S(1), sp.pi, sp.sqrt(2), sp.sqrt(3), sp.sqrt(5), sp.sqrt(6)]: v = np.float64(alpha/multiplier2*r) round_v = float(np.float64(v).round()) if abs(float(v)-round_v) < precision: simple = sign*sp.Rational(round_v, r)*multiplier*multiplier2 return simple, str(simple) if mult10 is None: mult10 = 0 while alpha and alpha < 1: mult10 += 1 alpha = alpha * 10 else: change_order = mult10 while change_order > 0: alpha = alpha * 10 change_order -= 1 if mult10 <= 3: while mult10: alpha = alpha / 10 mult10 -= 1 alpha = float(np.float64(alpha/precision).round()) * precision simple_str = str(sp.S(alpha)) if simple_str.find(".") != -1: for i in range(len(simple_str)-1, 0, -1): if simple_str[i] == "0": simple_str = simple_str[:i] continue if simple_str[i] == ".": simple_str = simple_str[:i] break if sign < 1: simple_str = "-" + simple_str if mult10: simple_str += "e-%d" % mult10 if multiplier != 1: simple_str += "*"+str(multiplier) return sp.S(sign*alpha*10**(-mult10)), simple_str def simple_complex(c, precision=1e-6, nsimplify=True, fracmax=63): r = c.real z = c.imag mult10 = None if r != 0 and z != 0: mult10 = 0 _r = r _z = z while abs(_r) < 1 and abs(_z) < 1: mult10 += 1 _r = _r * 10 _z = _z * 10 (spr, cr) = simple_float(r, precision, nsimplify, fracmax, mult10=mult10) (spz, cz) = simple_float(z, precision, nsimplify, fracmax, multiplier=sp.I, mult10=mult10) if cz == "0": return spr, cr if cr == "0": return spz*sp.I, cz if cz[0] != "-": return spr+spz*sp.I, cr+"+"+cz else: return spr+spz*sp.I, cr+cz def format_parameters(params: dict, precision: float = 1e-6, nsimplify: bool = True, separator: str = '\n') -> str: """ Prepares a string output from a dictionary of parameters. params: dictionary where keys are the parameter names and values are the corresponding parameter value. Values can either be a string or a float. precision: Rounds a float value to the given precision nsimplify: Try to simplify numerical display in case of float value separator: String separator for the final join """ output = [] for key, value in params.items(): if not isinstance(value, str): _, value = simple_float(value, precision, nsimplify) output.append(f'{key}={value}') return separator.join(output) ================================================ FILE: perceval/utils/globals.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. global_params = { "min_p": 1e-16, "min_complex_component": 1e-6, "min_precision_gate": 1e-4 } ================================================ FILE: perceval/utils/logging/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import functools import sys from exqalibur import logging as xq_log from .config import LoggerConfig from .loggers import ExqaliburLogger, PythonLogger _logger = None level = xq_log.level channel = xq_log.channel def get_logger(): global _logger return _logger def _my_excepthook(excType, excValue, this_traceback): # only works for the main thread _logger.critical("Uncaught exception!", channel=channel.general, exc_info=(excType, excValue, this_traceback)) def deprecated(*decorator_args, **decorator_kwargs): def decorator_deprecated(func): @functools.wraps(func) def wrapper_deprecated(*args, **kwargs): log = f"DeprecationWarning: Call to deprecated function (or staticmethod) {func.__name__}." if "reason" in decorator_kwargs: log += f" ({decorator_kwargs['reason']})" if "version" in decorator_kwargs: log += f" -- Deprecated since version {decorator_kwargs['version']}" _logger.warn(log, channel.user) return func(*args, **kwargs) return wrapper_deprecated return decorator_deprecated def use_python_logger(logger = None): global _logger if isinstance(_logger, PythonLogger) and _logger._logger == logger: return if _logger is not None: _logger.info("Switching to Python logger", channel.general) _logger = PythonLogger(logger) sys.excepthook = _my_excepthook def use_perceval_logger(): global _logger if isinstance(_logger, ExqaliburLogger): return if _logger is not None: _logger.info("Switching to exqalibur logger", channel.general) _logger = ExqaliburLogger() _logger.initialize() sys.excepthook = _my_excepthook def apply_config(config: LoggerConfig): if config.python_logger_is_enabled(): use_python_logger() else: use_perceval_logger() global _logger _logger.apply_config(config) use_perceval_logger() ================================================ FILE: perceval/utils/logging/config.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import warnings from exqalibur import logging as exqalibur_logging from ..persistent_data import PersistentData _LOGGING = "logging" _CHANNELS = "channels" _ENABLE_FILE = "enable_file" _USE_PYTHON_LOGGER = "use_python_logger" _CHANNEL_NAMES = list(exqalibur_logging.channel.__members__.keys()) class LoggerConfig(dict): """This class represent the logger configuration as a dictionary and can be used to save it into persistent data. On class initialization, the configuration will be loaded from persistent data. """ def __init__(self): super().__init__() self.reset() self._persistent_data = PersistentData() self._load_from_persistent_data() def _init_channel(self, channel: exqalibur_logging.channel, level: exqalibur_logging.level = exqalibur_logging.level.off): self[_CHANNELS][channel.name] = {} self[_CHANNELS][channel.name]["level"] = level.name def reset(self): """Reset the logger configuration to its default value, which is: - Disable file - Channel user at level warning - Channels general & resources off """ self[_USE_PYTHON_LOGGER] = False self[_ENABLE_FILE] = False self[_CHANNELS] = {} for name in [exqalibur_logging.channel.general, exqalibur_logging.channel.resources]: self._init_channel(name) self._init_channel(exqalibur_logging.channel.user, exqalibur_logging.level.warn) def _load_from_persistent_data(self): config = self._persistent_data.load_config() try: if config and _LOGGING in config: config = config[_LOGGING] if _CHANNELS in config: for key in config[_CHANNELS]: if key in _CHANNEL_NAMES: self[_CHANNELS][key] = config[_CHANNELS][key] if _ENABLE_FILE in config: self[_ENABLE_FILE] = config[_ENABLE_FILE] if _USE_PYTHON_LOGGER in config: self[_USE_PYTHON_LOGGER] = config[_USE_PYTHON_LOGGER] except KeyError as e: warnings.warn(UserWarning(f"Incorrect logger config, try to reset and save it. {e}")) def set_level(self, level: exqalibur_logging.level, channel: exqalibur_logging.channel): """Set the level of a channel in the configuration Warning: this will not change the current logger level but only the level of the channel in the current LoggerConfig instance :param level: _description_ :param channel: _description_ """ self[_CHANNELS][channel.name]["level"] = level.name def use_python_logger(self): """Set the config to use the python logger Warning: this will not change the current logger level but only the level of the channel in the current LoggerConfig instance """ self[_USE_PYTHON_LOGGER] = True def use_perceval_logger(self): """Set the config to use the perceval logger Warning: this will not change the current logger level, but only the level of the channel in the current LoggerConfig instance """ self[_USE_PYTHON_LOGGER] = False def python_logger_is_enabled(self): return self[_USE_PYTHON_LOGGER] def enable_file(self): """Enable to save the log into a file in the configuration Warning: this will not change the current logger file saving, but only the file saving of the current LoggerConfig instance """ self[_ENABLE_FILE] = True def disable_file(self): """Disable to save the log into a file in the configuration Warning: this will not change the current logger file saving, but only the file saving of the current LoggerConfig instance """ self[_ENABLE_FILE] = False def save(self): """Save the current logger configuration in the persistent data """ self._persistent_data.save_config({_LOGGING: dict(self)}) ================================================ FILE: perceval/utils/logging/loggers.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import json import logging import traceback import warnings import logging as py_log from abc import ABC, abstractmethod from os import path from exqalibur import logging as xq_log from ..persistent_data import PersistentData from .config import LoggerConfig, _CHANNELS DEFAULT_CHANNEL = xq_log.channel.user class ALogger(ABC): @abstractmethod def apply_config(self, config: LoggerConfig): pass @abstractmethod def enable_file(self): pass @abstractmethod def disable_file(self): pass @abstractmethod def set_level(self, level: int, channel: xq_log.channel = DEFAULT_CHANNEL): pass @abstractmethod def debug(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL): pass @abstractmethod def info(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL): pass @abstractmethod def warn(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL): pass @abstractmethod def error(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL, exc_info=None): pass @abstractmethod def critical(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL, exc_info=None): pass def log_resources(self, my_dict: dict): """Log resources as a dictionary with: - level: info - channel: resources - serializing the dictionary as json so it can be easily deserialize :param my_dict: resources dictionary to log """ self.info(json.dumps(my_dict), xq_log.channel.resources) class ExqaliburLogger(ALogger): _ALREADY_INITIALIZED = False def initialize(self): if ExqaliburLogger._ALREADY_INITIALIZED: return ExqaliburLogger._ALREADY_INITIALIZED = True persistent_data = PersistentData() if persistent_data.is_writable(): xq_log.initialize(self.get_log_file_path()) else: xq_log.initialize() self._configure_logger(LoggerConfig()) xq_log.enable_console() def _configure_logger(self, logger_config: LoggerConfig): if _CHANNELS in logger_config: channels = list(xq_log.channel.__members__) levels = list(xq_log.level.__members__) for channel, level in logger_config[_CHANNELS].items(): level = level['level'] if channel not in channels: warnings.warn(UserWarning(f"Unknown channel {channel}")) return if level not in levels: warnings.warn(UserWarning(f"Unknown level {level}")) return xq_log.set_level( xq_log.level.__members__[level], xq_log.channel.__members__[channel]) def apply_config(self, config: LoggerConfig): if config.python_logger_is_enabled(): warnings.warn(UserWarning( "Cannot change type of logger from logger.apply_config, use perceval.utils.apply_config instead")) self._configure_logger(config) def get_log_file_path(self): return path.join(PersistentData().directory, "logs", "perceval.log") def enable_file(self): print(f"starting to write logs in {self.get_log_file_path()}") xq_log.enable_file() def disable_file(self): self.warn("Log to file is being stopped!") xq_log.disable_file() def set_level( self, level: xq_log.level, channel: xq_log.channel = DEFAULT_CHANNEL) -> None: self.info(f"Set log level to '{level.name}' for channel '{channel.name}'", channel.general) xq_log.set_level(level, channel) def debug(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL): xq_log.debug(str(msg), channel) def info(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL): xq_log.info(str(msg), channel) def warn(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL): xq_log.warn(str(msg), channel) def _format_exception(self, exc_info=None) -> str: if not exc_info: return "" return '\n' + ''.join(traceback.format_exception(exc_info[0], exc_info[1], exc_info[2])) def error(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL, exc_info=None): msg = str(msg) if exc_info: msg += self._format_exception(exc_info) traceback.print_exception(exc_info[0], exc_info[1], exc_info[2]) xq_log.error(str(msg), channel) def critical(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL, exc_info=None): msg = str(msg) if exc_info: msg += self._format_exception(exc_info) traceback.print_exception(exc_info[0], exc_info[1], exc_info[2]) xq_log.critical(str(msg), channel) class PythonLogger(ALogger): _level_ratio = 10 # Ratio between Python levels and exqalibur levels def __init__(self, logger: py_log.Logger = None): self._levels = { # Default values xq_log.channel.general.name: xq_log.level.off.value * self._level_ratio, xq_log.channel.user.name: xq_log.level.warn.value * self._level_ratio, xq_log.channel.resources.name: xq_log.level.off.value * self._level_ratio } if logger is None: # Create our own Python logger self._logger = py_log.getLogger("perceval") self._sh = logging.StreamHandler() formatter = logging.Formatter("%(asctime)s [%(levelname)s] - %(message)s") self._sh.setFormatter(formatter) self._logger.addHandler(self._sh) self._configure_levels(LoggerConfig()) else: # Work with an external logger self._logger = logger self._sh = None init_level = logger.getEffectiveLevel() self.set_level(init_level, xq_log.channel.general) self._logger.addFilter(self._message_has_to_be_logged) @staticmethod def _get_levelno(level_name: str): return xq_log.level.__members__.get(level_name, xq_log.level.off).value * PythonLogger._level_ratio def _configure_levels(self, config: LoggerConfig): self._levels = { name: self._get_levelno(channel["level"]) for name, channel in config[_CHANNELS].items() } def apply_config(self, config: LoggerConfig): if not config.python_logger_is_enabled(): warnings.warn(UserWarning( "Cannot change type of logger from logger.apply_config, use perceval.utils.apply_config instead")) self._configure_levels(config) def _message_has_to_be_logged(self, record) -> bool: if "channel" in record.__dict__: return record.levelno >= self._levels[record.channel] return True def enable_file(self): self.warn("This method have no effect. Use module logging to configure python logger") def disable_file(self): self.warn("This method have no effect. Use module logging to configure python logger") def set_level(self, level: xq_log.level, channel: xq_log.channel = DEFAULT_CHANNEL): if not isinstance(level, int): level = self._get_levelno(level.name) min_level = min(self._levels.values()) if level < min_level: # If the expected level is lower than the current min of channel levels, self._logger.setLevel(level) # we lower it in the stored Python logger instance if self._sh is not None: self._sh.setLevel(level) self._levels[channel.name] = level def debug(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL): self._logger.debug(msg, extra={"channel": channel.name}) def info(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL): self._logger.info(msg, extra={"channel": channel.name}) def warn(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL): self._logger.warning(msg, extra={"channel": channel.name}) def error(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL, exc_info=None): self._logger.error( msg, exc_info=exc_info, extra={"channel": channel.name}) def critical(self, msg: str, channel: xq_log.channel = DEFAULT_CHANNEL, exc_info=None): self._logger.critical( msg, exc_info=exc_info, extra={"channel": channel.name}) ================================================ FILE: perceval/utils/logical_state.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. class LogicalState(list): """Represent a Logical state :param state: Can be either None, a list or a str, defaults to None (empty list) :raises ValueError: Must have only 0 and 1 in a state :raises TypeError: Supports only None, list or str as state type """ def __init__(self, state: list[int] or str = None): if state is None: super().__init__([]) return if isinstance(state, str): state = [int(elem) for elem in state] if isinstance(state, list): if state.count(0) + state.count(1) != len(state): raise ValueError("A logical state should only contain 0s and 1s") super().__init__(state) return raise TypeError(f"LogicalState can be initialise with None, list or str, here {type(state)}") def __add__(self, other): temp = self.copy() temp.extend(other) return temp def __str__(self): if not self: return "|>" return '|' + ''.join([str(x) for x in self]) + '>' def generate_all_logical_states(n : int) -> list[LogicalState]: """ Generate all Logical states of size n :param n: Size of the Logical states to generate :return: List of all :math:`2^n` Logical states of size n """ format_str = f"#0{n+2}b" logical_state_list = [] for i in range(2**n): states = format(i, format_str)[2:] logical_state_list.append(LogicalState([int(state) for state in states])) return logical_state_list ================================================ FILE: perceval/utils/matrix.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing import io import re from abc import ABC, abstractmethod from scipy.linalg import sqrtm, block_diag, svd from collections.abc import Iterator import numpy as np import sympy as sp class Matrix(ABC): """ This parent class is the gateway :class:`MatrixN` or :class:`MatrixS` - based on ``use_symbolic``, and checking if input contains any parameter, it will create an instance of one or the other class. * :class:`MatrixS` is a subclass of :class:`sympy.Matrix` with slight API augmentations for compatibility with numpy * :class:`MatrixN` is a subclass of :class:`numpy.ndarray` Both classes have additional utility functions - while Matrix class is also presenting additional static utility functions """ @staticmethod def __new__(cls, source, use_symbolic=None): """Constructor for Matrix class :param source: can be a string, a file, a list, a ndarray, another Matrix, or a integer :param use_symbolic: True to force use of symbolic, False to force use of numeric, None to select based on source """ if isinstance(source, str): source = Matrix._read(source.split("\n")) elif isinstance(source, io.TextIOWrapper): source = Matrix._read(source) elif isinstance(source, tuple): assert len(source) == 1 or len(source) == 2, "Matrix only supports 1/2D-Matrices" if len(source) == 1: source = np.ndarray((source[0], 1)) else: source = np.ndarray(source) elif isinstance(source, sp.Matrix) or isinstance(source, np.ndarray): pass elif isinstance(source, list): if use_symbolic: source = sp.Matrix(source) else: source = np.asarray(source) elif isinstance(source, int): source = np.ndarray((source, source)) else: raise NotImplementedError("no implemented input parser") if use_symbolic is None and isinstance(source, sp.Matrix) and len(source.free_symbols): use_symbolic = True if use_symbolic: return MatrixS(source) else: if isinstance(source, sp.Matrix): if len(source.free_symbols): raise ValueError("cannot use MatrixN for matrix with parameters") source = np.array(source, dtype=complex) return MatrixN(source) @staticmethod def eye(n: int, use_symbolic: bool = False) -> Matrix: """Returns an identity matrix :param n: size of the matrix :param use_symbolic: defines if matrix will be symbolic or numeric :return: an identity matrix """ if use_symbolic: return MatrixS(sp.eye(n)) return MatrixN(np.eye(n, dtype=complex)) @staticmethod def zeros(shape: tuple[int, int], use_symbolic: bool = False) -> Matrix: """Generate an empty matrix :param shape: 2D shape of the matrix :param use_symbolic: defines if matrix will be symbolic or numeric :return: an empty matrix """ if use_symbolic: return MatrixS(sp.zeros(rows=shape[0], cols=shape[1])) return MatrixN(np.zeros(shape)) def is_square(self) -> bool: return len(self.shape) == 2 and self.shape[0] == self.shape[1] @abstractmethod def is_unitary(self) -> bool: """check if matrix is unitary""" @abstractmethod def is_symbolic(self) -> bool: """check if matrix is symbolic or numeric""" @property @abstractmethod def defined(self): pass @abstractmethod def tonp(self): pass @abstractmethod def tosp(self): pass @staticmethod def random_unitary(n: int) -> MatrixN: r"""static method generating a random unitary matrix :param n: size of the Matrix :return: a numeric Matrix """ u = np.random.randn(n, n) + 1j*np.random.randn(n, n) return Matrix._unitarize_matrix(n, u) @staticmethod def parametrized_unitary(n: int, parameters: np.ndarray | list) -> MatrixN: r"""static method generating a parametrized unitary matrix :param n: size of the Matrix :param parameters: :math:`2n^2` parameters to use as generator :return: a numeric Matrix """ assert len(parameters) == 2*n**2, "parameters do not have the right size: should be %d, and is %d" % ( 2*n**2, len(parameters)) a = np.reshape(parameters[:n**2], (n, n)) b = np.reshape(parameters[n**2:], (n, n)) u = a + 1j * b return Matrix._unitarize_matrix(n, u) @staticmethod def _unitarize_matrix(n: int, u: np.ndarray) -> MatrixN: # makes an 'n x n' matrix 'u' unitary (q, r) = np.linalg.qr(u) r_diag = np.sign(np.diagonal(np.real(r))) n_u = np.zeros((n, n)) np.fill_diagonal(n_u, val=r_diag) return MatrixN(np.matmul(q, n_u)) @staticmethod def get_unitary_extension(M: np.ndarray) -> MatrixN: r"""Embed the input matrix M into a unitary matrix U U = | M/σ * | | * * | .. math:: U = \begin{matrix} M/σ & * \\ * & * \end{matrix} :param M: np.ndarray describing a row x col complex matrix :return: numeric matrix describing a (row + col) x (row + col) complex unitary matrix """ row, col = M.shape flag_transpose = row > col if flag_transpose: M = M.T row, col = M.shape # Singular value decomposition and normalisation v1, s, v2h = svd(M) d = np.diag(s / np.max(s)) # # Unitary extension V1 = block_diag(v1, v2h.T.conj()) V2h = block_diag(v2h, v1.T.conj()) D = np.block([[d, np.zeros((row, col - row)), sqrtm(np.eye(row) - d**2)], [np.zeros((col - row, row)), np.eye(col - row), np.zeros((col - row, row))], [sqrtm(np.eye(row) - d**2), np.zeros((row, col - row)), -d]]) U = V1 @ D @ V2h if flag_transpose: U = U.T return MatrixN(U) def simp(self): """Simplify the matrix - only implemented for symbolic matrix""" return self @staticmethod def _read(seqline: Iterator[str]) -> Matrix: """read a matrix from file or a string sequence""" rows = [] n = None for line in seqline: line = line.strip("\n ⎡⎤⎥⎢⎣⎦|[]") if not line or line.startswith("#"): continue row = [sp.S(s) for s in re.split(r"[\t ]+", line) if s] if n: if len(row) != n: raise ValueError("invalid matrix") else: n = len(row) rows.append(row) return sp.Matrix(rows) @abstractmethod def fill(self, _: float): pass @abstractmethod def __getitem__(self, k): pass class MatrixS(Matrix, sp.Matrix): def __new__(cls, obj): return sp.Matrix.__new__(cls, obj) def is_symbolic(self): return True @property def defined(self): return not self.free_symbols def tonp(self): return MatrixN(np.array(self, dtype=complex)) def tosp(self): return self @property def T(self): """ use numpy language """ return self.transpose() def fill(self, f): return sp.Matrix.fill(self, f) def __getitem__(self, k): return sp.Matrix.__getitem__(self, k) def conj(self): """ use numpy language """ return self.conjugate() def simp(self): for i in range(self.shape[0]): for j in range(self.shape[1]): self[i, j] = self[i, j].simplify() return self @property def ndim(self): return 2 def is_unitary(self): """check if a matrix is squary and unitary""" if not self.is_square(): return False if self.free_symbols: # use sympy only if we really have to... p_trans = self * self.T.conj()-sp.eye(self.shape[0]) if p_trans.free_symbols: # cannot decide return None return np.allclose(np.array(p_trans).astype(complex), np.zeros(self.shape)) else: return np.allclose(self.tonp().dot(self.tonp().T.conj()), np.eye(self.shape[0])) class MatrixN(np.ndarray, Matrix): def __new__(cls, obj): array = super().__new__(cls, shape=obj.shape, dtype=complex) np.copyto(array, obj, casting='safe') return array @property def defined(self): return True def is_symbolic(self): return False def tonp(self): return self def tosp(self): return MatrixS(self) def fill(self, f): return np.ndarray.fill(self, f) def __getitem__(self, k): return np.ndarray.__getitem__(self, k) def is_unitary(self): """check if a matrix is square and unitary""" if not self.is_square(): return False return np.allclose(self.dot(self.T.conj()), np.eye(self.shape[0])) def inv(self) -> MatrixN: """returns inverse of the Matrix :return: """ return np.linalg.inv(self) def matrix_double(u: Matrix): m = u.shape[0] pu = Matrix(m * 2, u.is_symbolic()) pu.fill(0) for k1 in range(0, m): for k2 in range(0, m): pu[2 * k1, 2 * k2] = u[k1, k2] pu[2 * k1 + 1, 2 * k2 + 1] = u[k1, k2] return pu ================================================ FILE: perceval/utils/mlstr.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import re def _align(s): m = max([len(s) for s in s.split("\n")]) return "\n".join([ls+" "*(m-len(ls)) for ls in s.split("\n")]) # noinspection PyPep8Naming class mlstr: """ multiline str utility - useful to build strings with multiline blocks should behave mostly as a string except for that specificity """ def __init__(self, *value): self._s = str(*value) def __iadd__(self, s): S = s n = len(self._s.split("\n")) m = len(S.split("\n")) if n > m: S += "\n" * (n-m) else: self._s += "\n" * (m-n) self._s = "\n".join([s1+s2 for (s1, s2) in zip(_align(self._s).split("\n"), S.split("\n"))]) return self def __radd__(self, s): return mlstr(s)+self def __add__(self, s): s0 = self._s S = str(s) n = len(s0.split("\n")) m = len(S.split("\n")) if n > m: S += "\n" * (n-m) else: s0 += "\n" * (m-n) return mlstr("\n".join([s1+s2 for (s1, s2) in zip(_align(s0).split("\n"), S.split("\n"))])) def __str__(self): return self._s def __repr__(self): return self._s def split(self, sep): if sep == "\n": return self._s.split(sep) else: raise NotImplementedError @property def height(self): return len(self._s.split("\n")) def __mod__(self, args): pats = re.split(r'(%[.0]*[sdf])', self._s) s = mlstr('') for i, p in enumerate(pats): if i % 2: s += p % args[i >> 1] else: s += p return s def join(self, list_str): S = mlstr("") for idx, mls in enumerate(list_str): if idx: S += self._s S += mls return S ================================================ FILE: perceval/utils/noise_model.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from ._validated_params import AValidatedParam, ValidatedBool, ValidatedFloat from math import pi class NoiseModel: """ The NoiseModel class contains all noise parameters which are supported by Perceval. Default value of each parameter means "no noise", so a NoiseModel constructed with all default parameters leads to a perfect simulation. :param brightness: first lens brightness of a quantum dot :param indistinguishability: chance two photons are indistinguishable :param g2: g²(0) - second order intensity autocorrelation at zero time delay. This parameter is correlated with how often two photons are emitted by the source instead of a single one. :param g2_distinguishable: g2-generated photons indistinguishability :param transmittance: system-wide transmittance (warning, can interfere with the brightness parameter) :param phase_imprecision: maximum precision of the phase shifters (0 means infinite precision) :param phase_error: maximum random noise on the phase shifters (in radian) """ # Source parameters brightness = ValidatedFloat(0, 1, 1) indistinguishability = ValidatedFloat(0, 1, 1) g2 = ValidatedFloat(0, 1, 0) g2_distinguishable = ValidatedBool(True) # System-wide parameter transmittance = ValidatedFloat(0, 1, 1) # Optical circuit parameter phase_imprecision = ValidatedFloat(0, default_value=0) phase_error = ValidatedFloat(0, pi, 0) def __init__(self, brightness: float = None, indistinguishability: float = None, g2: float = None, g2_distinguishable: bool = None, transmittance: float = None, phase_imprecision: float = None, phase_error: float = None ): self.brightness = brightness self.indistinguishability = indistinguishability self.g2 = g2 self.g2_distinguishable = g2_distinguishable self.transmittance = transmittance self.phase_imprecision = phase_imprecision self.phase_error = phase_error def __deepcopy__(self, memo): return NoiseModel(**self.__dict__()) def __str__(self) -> str: return str(self.__dict__()) def __repr__(self) -> str: return str(self.__dict__()) def __dict__(self) -> dict: cls = type(self) res = {} for attr in dir(self): if not attr.startswith("__") and isinstance(cls.__dict__.get(attr), AValidatedParam): v = getattr(self, attr) if v != cls.__dict__[attr].default_value: res[attr] = v return res def __eq__(self, other) -> bool: cls = type(self) for attr in dir(self): if not attr.startswith("__") and isinstance(cls.__dict__.get(attr), AValidatedParam): if getattr(self, attr) != getattr(other, attr): return False return True ================================================ FILE: perceval/utils/parameter.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations import random from copy import deepcopy import sympy as sp from .logging import deprecated class Parameter: r"""A Parameter is a used as a variable in a circuit definition Parameters are a simple way to introduce named variables in a circuit. They take floating number values. When non defined they are associated to sympy symbols and will be used to perform symbolic calculations. :param name: name of the parameter :param value: optional value, when the value is provided at initialization, the parameter is considered as `fixed` :param min_v: minimal value that the parameter can take, is used in circuit optimization :param max_v: maximal value that the parameter can take, is used in circuit optimization :param periodic: True if the parameter is periodic, False otherwise (default True) """ def __init__(self, name: str, value: float = None, min_v: float = None, max_v: float = None, periodic: bool = True): self._min = None if min_v is None else float(min_v) self._max = None if max_v is None else float(max_v) if value is None: # Without a numerical value, create the symbol named after the parameter name self._symbol = sp.symbols(name, real=True) self._value = None else: self._symbol = None if not isinstance(value, sp.Expr): self._value = self._check_value(value, self._min, self._max, periodic) else: self._value = value self.name = name self._periodic = periodic self._original = None self._params = {self} # set of sub parameters @property def _is_expression(self): return False @property def spv(self) -> sp.Expr: """The current value of the parameter defined as a sympy expression""" if self._value is not None: return sp.S(self._value) else: return self._symbol @property def is_variable(self) -> bool: """Returns True for a non-fixed parameter""" return self._symbol is not None def __bool__(self) -> bool: return self.is_variable or float(self) != 0 def __float__(self): r"""Convert the parameter to float, will fail if the parameter has no defined value """ return float(self._value) def copy(self) -> Parameter: return deepcopy(self) def evalf(self, subs: dict = None) -> float: r"""Convert the parameter to float, will fail if the parameter has no defined value """ if subs is None or not isinstance(self._value, sp.Expr): return float(self._value) return self._value.evalf(subs=subs) def is_symbolic(self) -> bool: return self._value is None or isinstance(self._value, sp.Expr) def random(self): if self._symbol is None: return float(self._value) if self._min is not None and self._max is not None: return float(random.random() * (self._max-self._min) + self._min) return random.random() @staticmethod def _check_value(v: float, min_v: float, max_v: float, periodic: bool): if periodic and min_v is not None and max_v is not None: if v > max_v: p = int((v-max_v)/(max_v-min_v)) v = v - (p+1) * (max_v-min_v) elif v < min_v: p = int((min_v-v)/(max_v-min_v)) v = v + (p+1) * (max_v-min_v) if (min_v is not None and v < min_v) or (max_v is not None and v > max_v): raise ValueError("value %f out of bound [%f,%f]" %(v, min_v, max_v)) return v def check_value(self, v): return self._check_value(v, self._min, self._max, self._periodic) def set_value(self, v: float, force: bool = False): """Define the numerical value of a Parameter :param v: the numerical value :param force: enable to change the value of a fixed parameter :raise: `RuntimeError` if the parameter is fixed """ v = self._check_value(v, self._min, self._max, self._periodic) if self.fixed and not force: raise RuntimeError("cannot set fixed parameter", v, self._value) self._value = v def fix_value(self, v: float): """Fix the value of a non-fixed Parameter. The Parameter is not variable afterwards. :param v: the numerical value """ self._symbol = None self._value = self._check_value(v, self._min, self._max, self._periodic) def reset(self): r"""Reset the value of a non-fixed parameter""" if self._symbol: self._value = None @property def defined(self) -> bool: r"""Return True if the parameter has a value (fixed or non fixed) """ return self._value is not None @property def is_periodic(self) -> bool: r"""Return True if the parameter is defined as a period parameter """ return self._periodic def set_periodic(self, periodic): r"""set periodic flag""" self._periodic = periodic @property def fixed(self) -> bool: r"""Return True if the parameter is fixed """ return self._symbol is None def __repr__(self): return "Parameter(name='%s', value=%s%s%s)" % (str(self.name), str(self._value), self._min is not None and ", min_v="+str(self._min) or "", self._max is not None and ", max_v="+str(self._max) or "") @property def bounds(self) -> tuple[float, float]: r"""Minimal and maximal values for the parameter """ return self._min, self._max @property def min(self): r"""The minimal value of the parameter """ return self._min @min.setter def min(self, m: float): r"""Set the minimal value of the parameter """ self._min = m @property def max(self) -> float: r"""The maximal value of the parameter """ return self._max @max.setter def max(self, m: bool): r"""Set the maximal value of the parameter """ self._max = m @deprecated(version = "v1.1", reason = "Use id(self) or self.is_identical_to(other) instead") @property def pid(self): r"""Unique identifier for the parameter""" return id(self) def __mul__(self, other): if isinstance(other, Parameter): return Expression(f"({self.name}*{other.name})", self._merge_param_sets(other)) elif isinstance(other, (int, float)): return Expression(f"({other}*{self.name})", self._params) raise TypeError("Unsupported parameter operation.") def __rmul__(self, other): return self.__mul__(other) def __add__(self, other): if isinstance(other, Parameter): return Expression(f"({self.name}+{other.name})", self._merge_param_sets(other)) elif isinstance(other, (int, float)): return Expression(f"({self.name}+{other})", self._params) raise TypeError("Unsupported parameter operation.") def __radd__(self, other): return self.__add__(other) def __sub__(self, other): if isinstance(other, Parameter): return Expression(f"({self.name}-{other.name})", self._merge_param_sets(other)) elif isinstance(other, (int, float)): return Expression(f"({self.name}-{other})", self._params) raise TypeError("Unsupported parameter operation.") def __rsub__(self, other): if isinstance(other, (int, float)): return Expression(f"({other}-{self.name})", self._params) raise TypeError("Unsupported operation.") def __truediv__(self, other): if isinstance(other, Parameter): return Expression(f"({self.name}/{other.name})", self._merge_param_sets(other)) elif isinstance(other, (int, float)): return Expression(f"({self.name}/{other})", self._params) raise TypeError("Unsupported parameter operation.") def __pow__(self, other): if isinstance(other, Parameter): return Expression(f"({self.name}^{other.name})", self._merge_param_sets(other)) elif isinstance(other, (int, float)): return Expression(f"({self.name}^{other})", self._params) raise TypeError("Unsupported parameter operation.") def __neg__(self): # Ensure using __neg__ twice in a row returns the original parameter if self._original is not None: return self._original expr = Expression(f"(-{self.name})", self._params) expr._original = self return expr def _merge_param_sets(self, other): return self._params | other._params class Expression(Parameter): """ Symbolic math expression This class allows arithmetic manipulation of the Parameter class. Example: >>> p_a = Parameter("A") >>> p_b = Parameter("B") >>> sum_ab = Expression("A**2 + B", {p_a, p_b}) >>> p_a.set_value(2) >>> p_b.set_value(3) >>> print(float(sum_ab)) # Prints 7.0 :param name: string specifying equation, acts as name of Expression parameter. :param parameters: set of sub parameter instances used in the expression """ def __init__(self, name: str, parameters: set[Parameter]): try: e = sp.S(name) self.name = f"({e})" except Exception as err: raise ValueError(f"{name} is not an expression: {err}") if not isinstance(e, sp.Expr): raise ValueError (f"{name} is not an expression") super().__init__(self.name, periodic=False) # Create set containing all parent parameters self._params = set(parameters) self._check_parameters(set(fs.name for fs in e.free_symbols)) self._symbol = sp.S(name) @property def _is_expression(self): return True def _check_parameters(self, free_symbol_names): pcvl_param_names = set(p.name for p in self._params) if free_symbol_names != pcvl_param_names: raise RuntimeError(f"Missing parameters: {free_symbol_names - pcvl_param_names}") def __repr__(self): return f"Expression('{self.name[1:-1]}', parameters={self._params})" def __float__(self): """Updates Expression with respect to any changes made to parent Parameters""" if any(not param.defined for param in self._params): raise ValueError("Expression is symbolic, cannot compute its numerical value") return float(self.spv.subs({param.name : param._value for param in self._params})) @property def parameters(self) -> list[Parameter]: """Returns list of sub parameters in alphabetical order""" return sorted(self._params, key=lambda obj: obj.name) @property def is_periodic(self) -> bool: """Expressions are not considered periodic""" return False @property def defined(self) -> bool: r"""Return True if the parameter has a value (fixed or non fixed) """ return all([p.defined for p in self._params]) @property def fixed(self) -> bool: r"""Return True if the parameter is fixed """ return all(p.fixed for p in self._params) def copy(self) -> Expression: return deepcopy(self) P = Parameter E = Expression ================================================ FILE: perceval/utils/persistent_data.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import json import tempfile import warnings from platformdirs import PlatformDirs from .versions import PMetadata from ._enums import FileFormat _CONFIG_FILE_NAME = "config.json" SUB_DIRECTORIES = ['logs', 'job_group'] class PersistentData: r""" PersistentData is a class that stores data on the drive to save data between launches of perceval. On init, it creates a directory (if it doesn't exist) for storing perceval persistent data. This directory can be set via an environment variable PCVL_PERSISTENT_PATH. Default directory depends on the os: * Linux: '/home/my_user/.local/share/perceval-quandela' * Windows: 'C:\\Users\\my_user\\AppData\\Local\\quandela\\perceval-quandela' * Darwin: '/Users/my_user/Library/Application Support/perceval-quandela' If the default directory cannot be created or read/write in, a temporary directory will be used. If the configured directory or the temporary directory cannot be created or read/write in, an error will be raised. """ def __init__(self): is_default_folder = False # first, try the env var self._directory = os.getenv('PCVL_PERSISTENT_PATH') if not self._directory: # second, try the default folder self._directory = PlatformDirs(PMetadata.package_name(), PMetadata.author()).user_data_dir is_default_folder = True if not self._safe_create_directory(): if is_default_folder: # third, try the temp folder warnings.warn(UserWarning(f"Can't read or write in default folder {self._directory}.")) self._directory = tempfile.gettempdir() warnings.warn(UserWarning(f"Using temp folder {self._directory}.")) if not self._safe_create_directory(): raise PermissionError(f"Can't read or write in temp folder {self._directory}. " f"No folder is usable for persistent data. " f"Another folder can be set with the environment variable PCVL_PERSISTENT_PATH.") else: raise PermissionError(f"Can't read or write in configured folder {self._directory}. " f"Check the environment variable PCVL_PERSISTENT_PATH.") def is_writable(self) -> bool: """Return if the directory is writable :return: True if the directory is writable, False else """ return os.access(self._directory, os.W_OK) def is_readable(self) -> bool: """Return if the directory is readable :return: True if the directory is readable, False else """ return os.access(self._directory, os.R_OK) def _create_directory(self) -> None: """Create the persistent data root directory if it doesn't exist """ if not os.path.exists(self._directory): os.makedirs(self._directory) # by default, mode=0o777 def _safe_create_directory(self) -> bool: """Create the persistent data root directory if it doesn't exist """ try: self._create_directory() except OSError as exc: warnings.warn(UserWarning(f"{exc}")) return False if not self.is_writable() or not self.is_readable(): return False return True def get_folder_size(self) -> int: """Get the directory data size :return: directory data size in bytes """ return sum(os.path.getsize(os.path.join(dirpath, filename)) for dirpath, dirnames, filenames in os.walk(self._directory) for filename in filenames) def get_full_path(self, element_name: str) -> str: """Get the full path of an element supposedly in persistent data directory :param element_name: name of the element (with extension) :return: full path of the file """ return os.path.join(self._directory, element_name) def has_file(self, filename: str) -> bool: """Find if persistent data has file :param filename: name of the file to find (with extension) :return: True is the file exists, else False """ return os.path.exists(os.path.join(self._directory, filename)) def delete_file(self, filename: str): """Delete a file in persistent data directory if file doesn't exist, raise a user warning :param filename: name of the file to delete (with extension) """ file_path = self.get_full_path(filename) if not os.path.exists(file_path): warnings.warn(UserWarning(f"Cannot delete {file_path}, file doesn't exist")) return try: os.remove(file_path) except OSError: warnings.warn(UserWarning("Cannot delete persistent file {file_path}")) def write_file(self, filename: str, data: bytes | str, file_format: FileFormat): """Write data into a file in persistent data directory :param filename: name of the file to write in (with extension) :param data: data to write """ if file_format != FileFormat.BINARY and file_format != FileFormat.TEXT: raise NotImplementedError(f"format {format} is not supported") if self.is_writable(): file_path = self.get_full_path(filename) try: if file_format == FileFormat.BINARY: with open(file_path, "wb") as file: file.write(data) elif file_format == FileFormat.TEXT: with open(file_path, "wt", encoding="UTF-8") as file: file.write(data) else: warnings.warn(UserWarning(f"Can't save {filename}, unknown file format {file_format}.")) except OSError: warnings.warn(UserWarning(f"Can't save {filename}")) else: warnings.warn(UserWarning(f"Can't save {filename}")) def read_file(self, filename: str, file_format: FileFormat) -> bytes | str: """Read data from a file in persistent data directory :param filename: name of the file to read (with extension) :raises FileNotFoundError: Raise an exception if file is not found :return: data """ file_path = self.get_full_path(filename) if not os.path.exists(file_path): raise FileNotFoundError(file_path) data = None if file_format == FileFormat.BINARY: with open(file_path, "r+b") as file: data = file.read() data = data.removesuffix(b'\n') data = data.removesuffix(b' ') elif file_format == FileFormat.TEXT: with open(file_path, "r+t", encoding="UTF-8") as file: data = str(file.read()) data = data.removesuffix('\n').rstrip() else: raise NotImplementedError(f"format {format} is not supported") return data def load_config(self) -> dict: """Load perceval config from persistent data :return: config """ config = {} if self.has_file(_CONFIG_FILE_NAME): try: config = json.loads(self.read_file(_CONFIG_FILE_NAME, FileFormat.TEXT)) except (OSError, json.JSONDecodeError): warnings.warn("Cannot read config file") return config def save_config(self, config: dict): """Save config into persistent data, update any config previously saved :param config: config to save """ if self.is_writable(): file_config = self.load_config() file_config.update(config) self.write_file(_CONFIG_FILE_NAME, json.dumps(file_config), FileFormat.TEXT) else: warnings.warn(UserWarning(f"Can't save configuration to {_CONFIG_FILE_NAME} in {self._directory}.")) def clear_all_data(self): """Delete all persistent data except for log """ for file in os.listdir(self._directory): if all(keyword not in file for keyword in SUB_DIRECTORIES): self.delete_file(file) @property def directory(self) -> str: """return persistent data directory :return: persistent data directory """ return self._directory def create_sub_directory(self, relative_path: str) -> str: """ Creates a sub folder in persistent data directory if non-existent :param relative_path: the folders path to create relative to self.directory :return: full absolute path """ dir_path = os.path.join(self.directory, relative_path) try: if not os.path.exists(dir_path): os.makedirs(dir_path) except OSError as exc: warnings.warn(UserWarning(f"{exc}")) if not PersistentData._is_subdir_writable(dir_path): raise PermissionError(f"Write permission denied for sub-directory {relative_path}") if not PersistentData._is_subdir_readable(dir_path): raise PermissionError(f"Read permission denied for sub-directory {relative_path}") return dir_path @staticmethod def _is_subdir_writable(path_sub_dir): return os.access(path_sub_dir, os.W_OK) @staticmethod def _is_subdir_readable(path_sub_dir): return os.access(path_sub_dir, os.R_OK) ================================================ FILE: perceval/utils/polarization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing import numpy as np import re import sympy as sp from .states import BasicState from .matrix import Matrix POLARIZATION_MAPPING = { "H": (sp.S(0), sp.S(0)), "V": (sp.pi, sp.S(0)), "D": (sp.pi/2, sp.S(0)), "A": (sp.pi/2, sp.pi), "R": (sp.pi/2, 3*sp.pi/2), "L": (sp.pi/2, sp.pi/2), } class Polarization: r"""Polarization class This class is defined values used by polarization annotations `P` :param v: a string (``[HVADLR]``), a single angle or a pair of angle definition either symbolic or numeric. Angles should be in :math:`[0,\pi]` range. :raise: `ValueError` if the parameters are out of range, or invalid """ def __init__(self, v: str | any | tuple[any, any]): if isinstance(v, str): if v not in POLARIZATION_MAPPING: raise ValueError("undefined value '%s' for polarization" % v) self.theta_phi = POLARIZATION_MAPPING[v] elif isinstance(v, tuple): if len(v) != 2: raise ValueError("Polarization is defined by 2 angles") for vs in v: if isinstance(vs, sp.Expr): if len(vs.free_symbols): raise ValueError("Polarization cannot have variables") elif not isinstance(vs, (int, float, sp.Number)): raise ValueError("incorrect definition for polarization angle: %s" % str(vs)) if v[0] < sp.S("0") or v[0] > sp.S("pi"): raise ValueError("theta should be in [0,pi]") if v[1] < sp.S("0") or v[1] >= sp.S("2*pi"): raise ValueError("theta should be in [0,2*pi[") self.theta_phi = v elif isinstance(v, complex): self.theta_phi = (v.real, v.imag) elif isinstance(v, float) or isinstance(v, int): if v < sp.S("0") or v > sp.S("pi"): raise ValueError("theta should be in [0,pi]") self.theta_phi = (v, 0) else: raise ValueError("Polarization init should be string or tuple") def __complex__(self): return self.theta_phi[0]+1j*self.theta_phi[1] @staticmethod def parse(s: str) -> Polarization: r"""Parse a polarization value string :param s: should match regex: ``^([HVADLR]|\(theta,phi\)|theta$`` :return: a `Polarization` instance :raise: `ValueError` if the value cannot be parsed, or if parameters are invalid """ if re.match(r"^[HVADLR]$", s): return Polarization(s) if s[0] == "(": if s[-1] != ")": raise ValueError("incorrect format - missing closing parenthesis: %s" % s) lvs = s[1:-1].split(",") if len(lvs) > 2: raise ValueError("incorrect format - more than two parameters") angles = [] for v in lvs: try: angles.append(float(v)) except ValueError: try: angles.append(sp.S(v).simplify()) if angles[-1].free_symbols: raise ValueError("incorrect format - angle value should not contain variable in %s" % s) except ValueError: raise ValueError("tuple value (%s) have to be numeric expression in %s" % (v, s)) if len(angles) == 1: angles.append(0) return Polarization((angles[0], angles[1])) else: try: v = float(s) except ValueError: try: v = sp.S(s).simplify() except ValueError: raise ValueError("value has to be numeric expression in %s" % s) if v.free_symbols: raise ValueError("incorrect format - angle value should not contain variable in %s" % s) return Polarization((v, 0)) def project_eh_ev(self, use_symbolic=False) -> tuple[any, any]: r"""Build Jones vector corresponding to the current instance :return: a pair of numeric or symbolic expressions """ if use_symbolic: return sp.cos(self.theta_phi[0]/2), sp.exp(sp.I*self.theta_phi[1])*sp.sin(self.theta_phi[0]/2) else: return (np.cos(float(self.theta_phi[0])/2), (np.cos(float(self.theta_phi[1])) + 1j * np.sin(float(self.theta_phi[1])))\ * np.sin(float(self.theta_phi[0])/2)) def __str__(self): for key, (theta_phi_0, theta_phi_1) in POLARIZATION_MAPPING.items(): if theta_phi_0.equals(self.theta_phi[0]) and theta_phi_1.equals(self.theta_phi[1]): return key if self.theta_phi[1] == 0: return str(self.theta_phi[0]) return "(%s,%s)" % (str(self.theta_phi[0]), str(self.theta_phi[1])) def _rec_build_spatial_output_states(lfs: list, output: list): if len(lfs) == 0: yield BasicState(output) else: if lfs[0] == 0: yield from _rec_build_spatial_output_states(lfs[1:], output+[0, 0]) else: for k in range(lfs[0]+1): yield from _rec_build_spatial_output_states(lfs[1:], output+[k, lfs[0]-k]) def build_spatial_output_states(state: BasicState): yield from _rec_build_spatial_output_states(list(state), []) def _is_orthogonal(v1, v2, use_symbolic): if use_symbolic: orth = sp.conjugate(v1[0]) * v2[0] + sp.conjugate(v1[1]) * v2[1] return orth == 0 orth = np.conjugate(v1[0]) * v2[0] + np.conjugate(v1[1]) * v2[1] return abs(orth) < 1e-6 def convert_polarized_state(state: BasicState, use_symbolic: bool = False, inverse: bool = False) -> tuple[BasicState, Matrix]: r"""Convert a polarized BasicState into an expanded BasicState vector :param inverse: :param use_symbolic: :param state: :return: """ idx = 0 input_state = [] prep_matrix = None for k_m in range(state.m): input_state += [0, 0] if state[k_m]: vectors = [] for k_n in range(state[k_m]): # for each state we can handle up to two orthogonal vectors annot = state.get_photon_annotation(idx) idx += 1 v_hv = Polarization(annot.get("P", complex(Polarization(0)))).project_eh_ev(use_symbolic) v_idx = None for i, v in enumerate(vectors): if v == v_hv: v_idx = i break if v_idx is None: if len(vectors) == 2: raise ValueError("use statevectors to handle more than 2 orthogonal vectors") if len(vectors) == 0 or _is_orthogonal(vectors[0], v_hv, use_symbolic): v_idx = len(vectors) vectors.append(v_hv) else: raise ValueError("use statevectors to handle non orthogonal vectors") input_state[-2+v_idx] += 1 if vectors: eh1, ev1 = vectors[0] if len(vectors) == 1: if use_symbolic: eh2 = -sp.conjugate(ev1) ev2 = sp.conjugate(eh1) else: eh2 = -np.conjugate(ev1) ev2 = np.conjugate(eh1) else: eh2, ev2 = vectors[1] if prep_matrix is None: prep_matrix = Matrix.eye(2*state.m, use_symbolic) prep_state_matrix = Matrix([[eh1, eh2], [ev1, ev2]], use_symbolic) if inverse: prep_state_matrix = prep_state_matrix.inv() prep_matrix[2*k_m:2*k_m+2, 2*k_m:2*k_m+2] = prep_state_matrix return BasicState(input_state), prep_matrix ================================================ FILE: perceval/utils/postselect.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from typing import TypeAlias import exqalibur as xq from .states import BSDistribution, StateVector PostSelect: TypeAlias = xq.PostSelect def post_select_distribution( bsd: BSDistribution, postselect: PostSelect, heralds: dict = None, keep_heralds: bool = True) -> tuple[BSDistribution, float]: """Post select a BSDistribution :param bsd: BSDistribution to post select :param postselect: Post-selection conditions to apply :param heralds: Heralds to apply, defaults to None :param keep_heralds: Keep heralded modes in the BSDistribution (heralded modes will be removed from Fock states), defaults to True :return: A tuple containing post-selected BSDistribution and logical performance """ if not (postselect.has_condition or heralds): if len(bsd): bsd.normalize() return bsd, 1 if heralds is None: heralds = {} logical_perf = 1 result = BSDistribution() for state, prob in bsd.items(): heralds_ok = True for m, v in heralds.items(): if state[m] != v: heralds_ok = False if heralds_ok and postselect(state): if not keep_heralds: state = state.remove_modes(list(heralds.keys())) result[state] = prob else: logical_perf -= prob if len(result): result.normalize() return result, logical_perf def post_select_statevector( sv: StateVector, postselect: PostSelect, heralds: dict = None, keep_heralds: bool = True) -> tuple[StateVector, float]: """Post select a state vector :param sv: State Vector to post select :param postselect: post selection to apply :param heralds: heralds to apply, defaults to None :param keep_heralds: Keep heralded modes in the BSDistribution (heralded modes will be removed from Fock states), defaults to True :return: A tuple containing the post-selected StateVector and logical performance """ if not (postselect.has_condition or heralds): if len(sv): sv.normalize() return sv, 1 if heralds is None: heralds = {} logical_perf = 0 result = StateVector() for state, ampli in sv.unnormalized_iterator(): heralds_ok = True for m, v in heralds.items(): if state[m] != v: heralds_ok = False if heralds_ok and postselect(state): if not keep_heralds: state = state.remove_modes(list(heralds.keys())) result += ampli * state logical_perf += abs(ampli) ** 2 if len(result): result.normalize() return result, logical_perf ================================================ FILE: perceval/utils/progress_cb.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. def partial_progress_callable(progress_cb: callable, min_val: float = 0., max_val: float = 1.): """ Takes a progress cb and returns another progress_cb that calls the original one with linearly modified value, so that evaluating it at 0 evaluates the original one at min_val, and evaluating it at 1 evaluates the original one at max_val """ def partial_progress_cb(progress: float, message: str): prog = max_val * progress + min_val * (1 - progress) return progress_cb(prog, message) if progress_cb: return partial_progress_cb return None ================================================ FILE: perceval/utils/qmath.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np from scipy.spatial.distance import cdist def exponentiation_by_squaring(base, power: int): """Calculate the result of base^power i.e. base**power using exponentiation by squaring (or square-and-multiply) Args: :param base: the element to exponentiate :param power: *strictly positive* integer power :param result: the initialisation of the result """ if power < 1: raise ValueError("Power value must be strictly positive") if isinstance(base, int): temp_base = base result = base else: temp_base = base.__copy__() result = base.__copy__() power -= 1 while True: # If power is odd if power % 2 == 1: result = result * temp_base # Divide the power by 2 power = power // 2 if power == 0: break # Multiply base to itself temp_base = temp_base * temp_base return result def distinct_permutations(iterable, r=None): """Yield successive distinct permutations of the elements in *iterable*. >>> sorted(distinct_permutations([1, 0, 1])) [(0, 1, 1), (1, 0, 1), (1, 1, 0)] Equivalent to ``set(permutations(iterable))``, except duplicates are not generated and thrown away. For larger input sequences this is much more efficient. Duplicate permutations arise when there are duplicated elements in the input iterable. The number of items returned is `n! / (x_1! * x_2! * ... * x_n!)`, where `n` is the total number of items input, and each `x_i` is the count of a distinct item in the input sequence. If *r* is given, only the *r*-length permutations are yielded. >>> sorted(distinct_permutations([1, 0, 1], r=2)) [(0, 1), (1, 0), (1, 1)] >>> sorted(distinct_permutations(range(3), r=2)) [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] This code is a copy of the method of the repository https://github.com/more-itertools/more-itertools/ """ # Algorithm: https://w.wiki/Qai def _full(A): while True: # Yield the permutation we have yield tuple(A) # Find the largest index i such that A[i] < A[i + 1] for i in range(size - 2, -1, -1): if A[i] < A[i + 1]: break # If no such index exists, this permutation is the last one else: return # Find the largest index j greater than j such that A[i] < A[j] for j in range(size - 1, i, -1): if A[i] < A[j]: break # Swap the value of A[i] with that of A[j], then reverse the # sequence from A[i + 1] to form the new permutation A[i], A[j] = A[j], A[i] A[i + 1:] = A[: i - size: -1] # A[i + 1:][::-1] # Algorithm: modified from the above def _partial(A, r): # Split A into the first r items and the last r items head, tail = A[:r], A[r:] right_head_indexes = range(r - 1, -1, -1) left_tail_indexes = range(len(tail)) while True: # Yield the permutation we have yield tuple(head) # Starting from the right, find the first index of the head with # value smaller than the maximum value of the tail - call it i. pivot = tail[-1] for i in right_head_indexes: if head[i] < pivot: break pivot = head[i] else: return # Starting from the left, find the first value of the tail # with a value greater than head[i] and swap. for j in left_tail_indexes: if tail[j] > head[i]: head[i], tail[j] = tail[j], head[i] break # If we didn't find one, start from the right and find the first # index of the head with a value greater than head[i] and swap. else: for j in right_head_indexes: if head[j] > head[i]: head[i], head[j] = head[j], head[i] break # Reverse head[i + 1:] and swap it with tail[:r - (i + 1)] tail += head[: i - r: -1] # head[i + 1:][::-1] i += 1 head[i:], tail[:] = tail[: r - i], tail[r - i:] items = sorted(iterable) size = len(items) if r is None: r = size if 0 < r <= size: return _full(items) if (r == size) else _partial(items, r) return iter(() if r else ((),)) def kmeans(features: np.ndarray, n_clusters: int, n_init: int = 10) -> np.ndarray: """ Manual KMeans implementation. Clusterizes the system in k subsets. :param features: Data points for clustering. :param n_clusters: Number of clusters. :param n_init: Number of times the k-means algorithm will be run with different centroid seeds. :return: Cluster labels. """ best_labels = None best_inertia = np.inf MAX_ITERATIONS = 300 for _ in range(n_init): # Initialize centroids randomly indices = np.random.choice(features.shape[0], n_clusters, replace=False) centroids = features[indices] for _ in range(MAX_ITERATIONS): # Maximum number of iterations # Assign points to the nearest centroid distances = cdist(features, centroids, 'euclidean') labels = np.argmin(distances, axis=1) # Compute new centroids new_centroids = np.array([features[labels == i].mean(axis=0) for i in range(n_clusters)]) # Check for convergence (if centroids do not change) if np.allclose(centroids, new_centroids): break centroids = new_centroids # Compute inertia (sum of squared distances to the nearest centroid) inertia = np.sum((features - centroids[labels]) ** 2) if inertia < best_inertia: best_inertia = inertia best_labels = labels return best_labels ================================================ FILE: perceval/utils/stategenerator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import networkx as nx import numpy as np from .states import BasicState, StateVector from .qmath import distinct_permutations from ._enums import Encoding from .logging import get_logger, channel class StateGenerator: """ StateGenerator class for conveniently generating common complex StateVectors :param encoding: for specifying the output format of the StateVector supported are Encoding.RAW, Encoding.DUAL_RAIL, Encoding.POLARIZATION :param polarization_base: (optional) you can provide your own polarization basis as a tuple of BasicStates. default=(BasicState("`|{P:H}>`"), BasicState("`|{P:V}>`") """ def __init__(self, encoding, polarization_base = None): polarization_base = polarization_base or (BasicState("|{P:H}>"), BasicState("|{P:V}>")) assert isinstance(encoding, Encoding), "You need to provide an encoding" if encoding == Encoding.RAW: self._zero_state = BasicState("|0>") self._one_state = BasicState("|1>") elif encoding == Encoding.DUAL_RAIL: self._zero_state = BasicState("|1,0>") self._one_state = BasicState("|0,1>") elif encoding == Encoding.POLARIZATION: if len(polarization_base[0]) != 1 or len(polarization_base[1]) != 1: raise ValueError("The BasicStates representing the polarization basis should only contain one mode") self._zero_state = polarization_base[0] self._one_state = polarization_base[1] else: raise ValueError("Only use RAW, DUAL_RAIL or POLARIZATION encoding.") def logical_state(self, state: list[int]): """ Generate a StateVector from a list of logical state :param state: list of bits :return: StateVector representing the logical state """ sv = StateVector() for bit in state: if bit == 0: sv = sv * self._zero_state elif bit == 1: sv = sv * self._one_state else: raise ValueError("The argument list corresponding to a logical state should only contain 0s and 1s") return sv def bell_state(self, state: str): r""" Generate a StateVector representing a Bell state from its name: - "phi+" = :math:`\frac{|0,0>+|1,1>}{\sqrt{2}}` - "phi-" = :math:`\frac{|0,0>-|1,1>}{\sqrt{2}}` - "psi+" = :math:`\frac{|0,1>+|1,0>}{\sqrt{2}}` - "psi-" = :math:`\frac{|0,1>-|1,0>}{\sqrt{2}}` :param state: name of the bell state you want to generate :return: StateVector for a bell state """ if state == "phi+": sv = StateVector(self._zero_state ** 2) + StateVector(self._one_state ** 2) return sv elif state == "phi-": sv = StateVector(self._zero_state ** 2) - StateVector(self._one_state ** 2) return sv elif state == "psi+": sv = StateVector(self._zero_state * self._one_state) + StateVector(self._one_state * self._zero_state) return sv elif state == "psi-": sv = StateVector(self._zero_state * self._one_state) - StateVector(self._one_state * self._zero_state) return sv raise ValueError("The state parameter must contain one of the Bell states as a string: phi+,phi-,psi+,psi-") def ghz_state(self, n: int): """ Generate a StateVector representing a (generalized) Greenberger-Horne-Zeilinger state :param n: order of the GHZ state :return: StateVector representing the GHZ state """ assert n > 2, "A (generalized) Greenberger-Horne-Zeilinger state is only defined for n>2" sv = StateVector(self._zero_state ** n) + StateVector(self._one_state ** n) return sv def graph_state(self, graph: nx.Graph): """ Generate a StateVector representing a graph state. :param graph: networkx.Graph object. Edge weights are ignored. :return: StateVector representing the graph state """ sv = StateVector() if graph.number_of_nodes() == 0: return sv basicstates = [self._one_state, self._zero_state] # generate all basic states for _ in range(1, graph.number_of_nodes()): for j in range(len(basicstates)): basicstates.append(basicstates[j] * self._one_state) basicstates[j] = basicstates[j] * self._zero_state # calculate signum of each BasicState and add it to the result StateVector (corresponding to Controlled Z Gate) for bs in basicstates: sgn = 1 for u, v in graph.edges: enclen = len(self._zero_state) posu = u * enclen posz = v * enclen if bs[posu:posu + enclen] == self._one_state and bs[posz:posz + enclen] == self._one_state: sgn = -1*sgn if sgn == -1: sv = sv - StateVector(bs) else: sv = sv + StateVector(bs) sv.normalize() return sv def dicke_state(self, n: int, k: int = None) -> StateVector: """Get the Dicke state :math:`|D(n,k)>` which is the equal superposition state of all :math:`C(n,k)` basis states of weight :math:`k` Mode number: * For RAW and Polarization: :math:`n` * For Dual rail encoding: :math:`2*n` Photon number: * For Raw encoding: :math:`k` * For Dual rail and Polarization encoding: :math:`n` :param n: Number of qubits equal to :math:`|1>_L` or photons :param k: Weight (Number of qubits or modes) :return: Dicke state vector """ if not isinstance(n, int): raise TypeError(f"n parameter should be an int and not {type(n)}") if n < 1: raise ValueError("Only support strictly positive one state number") if not k: k = 2*n else: if not isinstance(k, int): raise TypeError(f"k parameter should be an int and not {type(k)}") if k < n: get_logger().warn(f"Generating an empty state since {k} is smaller than {n}", channel.user) return StateVector() dicke_state = StateVector() array = [str(self._one_state)[1:-1]]*n + [str(self._zero_state)[1:-1]]*(k-n) for state in distinct_permutations(array): dicke_state += BasicState(f"|{','.join(state)}>") return dicke_state @staticmethod def zero_padded_state(n: int, m: int = None) -> BasicState: r""" Generate a :math:`|111...10...0>` BasicState with n photons and m modes. The result is independent of the encoding. :param n: Number of photons :param m: Number of modes. Default :math:`n` :return: BasicState of the shape :math:`|111...10...0>` """ if m is None: return BasicState(n * [1]) assert n <= m, "Cannot generate a zero-padded state with more photons than modes" return BasicState(n * [1] + (m - n) * [0]) @staticmethod def periodic_state(n: int, m: int = None) -> BasicState: r""" Generate a BasicState consisting of repeating :math:`|10>` n times. Pads the end of the state with zero photon modes. The result is independent of the encoding. :param n: Number of photons :param m: Number of modes. Default :math:`2n` (no padding at the end) :return: BasicState of the shape :math:`|1010...000>` """ state = BasicState([1, 0] * n) if m is not None: assert 2 * n <= m, "Cannot generate a periodic state with a number of modes less than twice the number of photons" state *= BasicState([0]) ** (m - state.m) return state @staticmethod def evenly_spaced_state(n: int, m: int) -> BasicState: """ Generate a BasicState where photons are evenly spaced. The result is independent of the encoding. :param n: Number of photons :param m: Number of modes. :return: BasicState where the photons are as spread out as possible in the modes. """ res = [0] * m if n == 1: # Special case: put the photon in the middle res[m // 2] = 1 else: # 1e-5 induces a very small bias but avoids getting m in the positions, which would be out of range positions = np.linspace(0, m - 1e-5, n, dtype=int) for pos in positions: res[pos] += 1 return BasicState(res) ================================================ FILE: perceval/utils/states.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Needed to annotate Generator from collections import defaultdict from exqalibur import Annotation from multipledispatch import dispatch from typing import Generator, Union, final, TypeAlias import exqalibur as xq import numpy as np FockState: TypeAlias = xq.FockState NoisyFockState: TypeAlias = xq.NoisyFockState AnnotatedFockState: TypeAlias = xq.AnnotatedFockState BSCount: TypeAlias = xq.BSCount BSSamples: TypeAlias = xq.BSSamples StateVector: TypeAlias = xq.StateVector BSDistribution: TypeAlias = xq.BSDistribution SVDistribution: TypeAlias = xq.SVDistribution class BasicStateMeta(type): _state_classes = {FockState, NoisyFockState, AnnotatedFockState} def __instancecheck__(cls, inst): """Implement isinstance(inst, cls).""" return cls.__subclasscheck__(type(inst)) def __subclasscheck__(cls, sub): """Implement issubclass(sub, cls).""" return any(c in cls._state_classes for c in sub.mro()) or sub is cls State: TypeAlias = Union[FockState, NoisyFockState, AnnotatedFockState] @final class BasicState(metaclass=BasicStateMeta): @dispatch(type, FockState) def __new__(cls, fs: FockState): return FockState(fs) @dispatch(type, NoisyFockState) def __new__(cls, fs: NoisyFockState): return NoisyFockState(fs) @dispatch(type, AnnotatedFockState) def __new__(cls, fs: AnnotatedFockState): return AnnotatedFockState(fs) @dispatch(type) def __new__(cls): return FockState() @dispatch(type, (list, tuple, np.ndarray)) def __new__(cls, photons: list[int]): return FockState(photons) @dispatch(type, int) def __new__(cls, m: int): return FockState(m) @dispatch(type, (list, tuple, np.ndarray), (list, tuple, np.ndarray)) def __new__(cls, photons: list[int], noise: list[int]): return NoisyFockState(FockState(photons), noise) @dispatch(type, str) def __new__(cls, source: str): parsed_state = xq.ParsedBasicState(source) if parsed_state.is_noisy(): return NoisyFockState(parsed_state) elif parsed_state.is_annotated(): return AnnotatedFockState(parsed_state) return FockState(parsed_state) # All the following methods are here to make the linter happy @property def m(self) -> int: """ :return: The number of modes """ return 0 @property def n(self) -> int: """ :return: The number of photons """ return 0 def __len__(self) -> int: return 0 def __eq__(self, other) -> bool: return False def __ne__(self, other) -> bool: return True def __getitem__(self, item): return 0 def __mul__(self, other: State | int | float | complex) -> State | StateVector | BSDistribution: return StateVector() def __rmul__(self, other: State | int | float | complex) -> State | StateVector | BSDistribution: return StateVector() def __pow__(self, power) -> State: return FockState() def __iter__(self) -> Generator[int, None, None]: yield 0 def merge(self, other: State) -> State: """ :param other: a BasicState with the same number of modes than self. :return: A new state for which the photons in one mode are the photons in this mode in ``self`` and in ``other``. the type of the state is automatically inferred from ``self`` and ``other`` so that it can contain all the information. Rules: FockState photons are converted to {0} for a NoisyFockState. NoisyFockState photons are converted to {_:`noise_tag`} for a AnnotatedFockState. """ def __add__(self, other) -> State | StateVector | BSDistribution: return StateVector() def __radd__(self, other) -> State | StateVector | BSDistribution: return StateVector() def __sub__(self, other) -> State | StateVector | BSDistribution: return StateVector() def __rsub__(self, other) -> State | StateVector | BSDistribution: return StateVector() @property def has_polarization(self) -> bool: return False @property def has_annotations(self) -> bool: return False def separate_state(self) -> list[FockState]: """ :return: A list of states where each state represents a collection of indistinguishable photons from the original state """ def split_state(self) -> dict[int, FockState]: """ :return: A dict of states where each state represents a collection of indistinguishable photons from the original state, associated with the noise tag they were defined with. """ def partition(self, photon_nb: list[int]) -> list[list[FockState]]: """ :param photon_nb: a list of photon numbers. The sum of this list must be equal to self.n :return: A list containing all lists of states such that the merge of all these states is self, where each state of the sublists has the number of photons specified in ``photon_nb``. """ def clear_annotations(self) -> FockState: """ :return: A new state where the photons are on the same modes as self, with no noise or annotations. """ def get_mode_annotations(self, mode: int) -> list[Annotation] | list[int]: """ :param mode: The mode to return annotations for. :return: A list of len self[mode] containing the annotations of mode ``mode``. They can be Annotation for AnnotatedFockState, or integers for NoisyFockState. """ def get_photon_annotation(self, photon: int) -> Annotation | int: """ :param photon: The photon to look at (ordered by ascending mode number) :return: The Annotation of the photon if self is a AnnotatedFockState, or the noise tag if self is a NoisyFockState. """ def inject_annotation(self, annot: Annotation | int) -> AnnotatedFockState | NoisyFockState: """ :param annot: The annotation to inject. If this is an Annotation, the result will be an AnnotatedFockState. If this is an integer, the result will be an NoisyFockState. :return: A new BasicState where all photons now have the given annotation. """ def mode2photon(self, mode: int) -> int: """ :param mode: The mode of the photon. :return: The index of the first photon in the given mode (when photons are ordered mode by mode), or -1 if the mode is empty. """ def photon2mode(self, n: int) -> int: """ :param n: The photon index. :return: The mode of the nth photon, where modes are in ascending order. """ def prodnfact(self) -> float: """ :return: The product self[0]! self[1]! ... self[m-1]! """ def remove_modes(self, modes: list[int]) -> State: """ :param modes: The list of modes to remove. :return: A new state with only the modes that are not in ``modes`` """ def set_slice(self, other: State, start: int, end: int) -> State: """ :param other: The state to replace part of ``self`` with. Must have :math:`end - start` modes :param start: The first mode to replace. :param end: The last mode to replace (excluded). :return: A new state, promoted to the smallest possible type given ``self`` and ``other``, where the section between start and end is ``other``, and the remaining comes from ``self``. """ def threshold_detection(self, nb: int = 1): """ :param nb: Maximum number of photons per mode. :return: a new FockState where the photon count of mode m is min(nb, self[m]) """ def allstate_array(input_state: BasicState, mask: xq.FSMask = None) -> xq.FSArray: m = input_state.m n = input_state.n if mask is not None: output_array = xq.FSArray(m, n, mask) else: output_array = xq.FSArray(m, n) output_array.generate() return output_array def allstate_iterator(input_state: BasicState | StateVector, mask: xq.FSMask = None) -> Generator[xq.FockState]: """Iterator on all possible output states compatible with mask generating StateVector :param input_state: a given input state vector :param mask: an optional mask :return: a single state in the Fock space of the input state. When all the space is covered, the iteration ends. """ m = input_state.m ns = input_state.n ns = [ns] if isinstance(ns, int) else list(ns) for n in ns: if mask is not None: output_array = xq.FSArray(m, n, mask) else: output_array = xq.FSArray(m, n) for output_state in output_array: yield output_state def max_photon_state_iterator(m: int, n_max: int): """Iterator on all possible output state on m modes with at most n_max photons :param m: number of modes :param n_max: maximum number of photons :return: a single state containing from 0 to n_max photons. When all the space is covered, the iteration ends. """ for n in range(n_max+1): output_array = xq.FSArray(m, n) for output_state in output_array: yield output_state @dispatch(StateVector, annot_tag=str) def anonymize_annotations(sv: StateVector, annot_tag: str = "a") -> StateVector: # This algo anonymizes annotations but is not enough to have superposed states exactly the same given the storage # order of BasicStates inside the StateVector m = sv.m annot_map = {} result = StateVector() for bs, pa in sv: if isinstance(bs, FockState): result += StateVector(bs) * pa else: s = [""] * m for i in range(bs.n): mode = bs.photon2mode(i) annot = bs.get_photon_annotation(i) if annot_map.get(str(annot)) is None: if isinstance(bs, NoisyFockState): annot_map[str(annot)] = f"{{{len(annot_map)}}}" else: annot_map[str(annot)] = f"{{{annot_tag}:{len(annot_map)}}}" s[mode] += annot_map[str(annot)] if isinstance(bs, NoisyFockState): result += StateVector(NoisyFockState("|" + ",".join([v and v or "0" for v in s]) + ">")) * pa else: result += StateVector(AnnotatedFockState("|" + ",".join([v and v or "0" for v in s]) + ">")) * pa result.normalize() return result @dispatch(SVDistribution, annot_tag=str) def anonymize_annotations(svd: SVDistribution, annot_tag: str = "a") -> SVDistribution: sv_dist = defaultdict(lambda: 0) for k, p in svd.items(): state = anonymize_annotations(k, annot_tag=annot_tag) sv_dist[state] += p return SVDistribution({k: v for k, v in sorted(sv_dist.items(), key=lambda x: -x[1])}) @dispatch(BSDistribution, int) def filter_distribution_photon_count(bsd: BSDistribution, min_photons_filter: int) -> tuple[BSDistribution, float]: """ Filter the states of a BSDistribution to keep only those having state.n >= min_photons_filter :param bsd: the BSDistribution to filter out :param min_photons_filter: the minimum number of photons required to keep a state :return: a tuple containing the normalized filtered BSDistribution and the probability that the state is kept """ if min_photons_filter == 0: return bsd, 1 res = BSDistribution({state: prob for state, prob in bsd.items() if state.n >= min_photons_filter}) perf = sum(res.values()) if len(res): res.normalize() return res, perf @dispatch(SVDistribution, int) def filter_distribution_photon_count(svd: SVDistribution, min_photons_filter: int) -> tuple[BSDistribution, float]: """ Filter the states of a BSDistribution to keep only those having state.n >= min_photons_filter :param svd: the SVDistribution to filter out :param min_photons_filter: the minimum number of photons required to keep a state :return: a tuple containing the normalized filtered BSDistribution and the probability that the state is kept """ if min_photons_filter == 0: return svd, 1 res = SVDistribution({state_v: prob for state_v, prob in svd.items() for state in state_v.keys() if state.n >= min_photons_filter}) perf = sum(res.values()) if len(res): res.normalize() return res, perf ================================================ FILE: perceval/utils/versions/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from .metadata import PMetadata ================================================ FILE: perceval/utils/versions/metadata.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import re from importlib.metadata import metadata class PMetadata: _NAME = "perceval" _PACKAGE_NAME = "perceval-quandela" _METADATA = metadata(_PACKAGE_NAME) _REGEX = re.compile(r"(\d+\.\d+(?:\.\d+)*)") @staticmethod def short_version() -> str: return PMetadata._REGEX.findall(PMetadata._METADATA["version"])[0] @staticmethod def version() -> str: return PMetadata._METADATA["version"] @staticmethod def package_name() -> str: return PMetadata._PACKAGE_NAME @staticmethod def author() -> str: return PMetadata._METADATA["author"] @staticmethod def name() -> str: return PMetadata._NAME ================================================ FILE: perceval/utils/versions/version_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from packaging.version import Version def keep_latest_versions(versions: list[str], mini: str = None) -> list[str]: """ Keep the highest patch for all major-minor versions. Drops all pre-release patches. :param versions: A list of version strings 'vM.m.p' :param mini: (optional) The minimum version to keep. All versions below this are dropped. :return: The ordered list of version strings with the highest patch number. """ if mini is not None: mini = Version(mini) version_dict: dict[tuple[int, ...], Version] = {} for one_version in versions: try: version = Version(one_version) except AttributeError: continue if not version.is_prerelease and (mini is None or version >= mini): # filter alpha,beta... major_minor = version.release[:2] if major_minor not in version_dict or version > version_dict[major_minor]: version_dict[major_minor] = version latest_versions = sorted(version_dict.values()) return list(map(lambda v: f"v{v}", latest_versions)) ================================================ FILE: pyproject.toml ================================================ [build-system] requires = [ "setuptools>=42", "wheel" ] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] markers = [ "long_test: marks tests as slow (deselect with '--skip-long-test')", "rendering: marks rendering tests" ] minversion = "6.0" addopts = "-ra" testpaths = "tests" ================================================ FILE: pytest.ini ================================================ [pytest] markers = long_test: marks tests as slow (deselect with '--skip-long-test') rendering: mark a test as rendering. ================================================ FILE: requirements.txt ================================================ . ================================================ FILE: setup.py ================================================ import os import re import setuptools import subprocess from glob import glob def getRemoteRoot(): base = 'https://raw.githubusercontent.com/Quandela/Perceval/' git_command = subprocess.run(["git", "describe", "--tags", "--exact-match"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, universal_newlines=True) if git_command.returncode == 0: return base + 'refs/tags/' + git_command.stdout.strip() + '/' # Not a tag. Should we raise a warning? git_command = subprocess.run(["git", "rev-parse", "HEAD"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, universal_newlines=True) if git_command.returncode == 0: return base + git_command.stdout.strip() + '/' # Some git error, leave path unchanged return None def addRemoteRootToImages(content, root): image_source_patterns = [ r'<\s*img\s+.*src\s*=\s*"([^"]*)".*>', # r"<\s*img\s+.*src\s*=\s*'([^']*)'.*>", # r'!\s*\[.*\]\s*\((.*)\)', # ![replacement text](link) ] current = 0 result = "" for x in re.finditer('|'.join(image_source_patterns), content): for i in range(len(image_source_patterns)): filename = x.group(i) if filename and os.path.isfile(filename): result += content[current:x.start(i)] + root + content[x.start(i):x.end(i)] current = x.end(i) return content if current == 0 else result + content[current:] with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() root = getRemoteRoot() if root: long_description = addRemoteRootToImages(long_description, root) # Package list is autogenerated to be any 'perceval' subfolder containing a __init__.py file package_list = [os.path.dirname(p).replace('\\', '.') for p in glob('perceval/**/__init__.py', recursive=True)] setuptools.setup( name="perceval-quandela", author="quandela", author_email="perceval@quandela.com", description="A powerful Quantum Photonic Framework", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/Quandela/Perceval", project_urls={ "Documentation": "https://perceval.quandela.net/docs/", "Source": "https://github.com/Quandela/Perceval", "Tracker": "https://github.com/Quandela/Perceval/issues" }, classifiers=[ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], packages=package_list, install_requires=['sympy~=1.12', 'numpy>=1.26,<3', 'scipy~=1.13', 'tabulate~=0.9', 'matplotlib<4', 'exqalibur~=1.1.0', 'multipledispatch<2', 'protobuf>=3.20.3', 'drawsvg>=2.0', 'requests<3', 'networkx>=3.1,<4', 'latexcodec<4', 'platformdirs<5', 'tqdm' ], setup_requires=["scmver"], python_requires=">=3.10,<3.15", scmver=True ) ================================================ FILE: tests/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/_test_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import re import pytest import time import warnings from functools import wraps from pathlib import Path from unittest.mock import MagicMock from perceval import BasicState, Parameter, Expression, CompiledCircuit from perceval.components import ACircuit, PS, IDetector, AFFConfigurator, FFConfigurator, FFCircuitProvider, \ BSLayeredPPNR, Detector, Experiment, Circuit, AProcessor, AComponent from perceval.components.abstract_component import AParametrizedComponent from perceval.utils import StateVector, SVDistribution from perceval.utils.logging import channel from perceval.rendering import Format, pdisplay_to_file from perceval.rendering.circuit import ASkin, PhysSkin from perceval.algorithm import AProcessTomography TEST_IMG_DIR = Path(__file__).resolve().parent / 'rendering' / 'imgs' def strip_line_12(s: str) -> str: return s.strip().replace(" ", "") def check_sv_close(sv1: StateVector, sv2: StateVector) -> bool: sv1.normalize() sv2.normalize() if len(sv1) != len(sv2): return False for s in sv1.keys(): if s not in sv2: return False if sv1[s] != pytest.approx(sv2[s]): return False return True def assert_sv_close(sv1: StateVector, sv2: StateVector): sv1.normalize() sv2.normalize() assert len(sv1) == len(sv2), f"{sv1} != {sv2} (len)" for s in sv1.keys(): assert s in sv2, f"{sv1} != {sv2} ({s} missing from the latter)" assert sv1[s] == pytest.approx(sv2[s]), f"{sv1} != {sv2} (amplitudes {sv1[s]} != {sv2[s]})" def compact_svd(svd: SVDistribution) -> SVDistribution: res = SVDistribution() for sv, prob in svd.items(): found = False for res_sv in res: if check_sv_close(sv.__copy__(), res_sv.__copy__()): res[res_sv] += prob found = True break if not found: res[sv] = prob return res def assert_svd_close(lhsvd, rhsvd): lhsvd = compact_svd(lhsvd) rhsvd = compact_svd(rhsvd) lhsvd.normalize() rhsvd.normalize() assert len(lhsvd) == len(rhsvd), f"len are different, {len(lhsvd)} vs {len(rhsvd)}" for lh_sv, lh_p in lhsvd.items(): found_in_rh = False for rh_sv, rh_p in rhsvd.items(): if not check_sv_close(lh_sv.__copy__(), rh_sv.__copy__()): continue found_in_rh = True assert pytest.approx(lh_p) == rh_p, f"different probabilities for {lh_sv}, {lh_p} vs {rh_p}" break assert found_in_rh, f"sv not found {lh_sv}" def assert_bsd_close(lhbsd, rhbsd): lhbsd.normalize() rhbsd.normalize() assert len(lhbsd) == len(rhbsd), f"len are different, {len(lhbsd)} vs {len(rhbsd)}" for lh_bs, prob in lhbsd.items(): assert rhbsd.get(lh_bs, 0) == pytest.approx(prob), f"different probabilities for {lh_bs}, {prob} vs {rhbsd[lh_bs]}" def assert_circuits_eq(c_a: Circuit, c_b: Circuit): assert c_a.ncomponents() == c_b.ncomponents() assert_component_list_eq(c_a._components, c_b._components) def assert_component_list_eq(comp_a, comp_b): assert len(comp_a) == len(comp_b) for (input_idx, input_comp), (output_idx, output_comp) in zip(comp_a, comp_b): assert isinstance(input_comp, type(output_comp)) assert list(input_idx) == list(output_idx) if isinstance(input_comp, AParametrizedComponent): for param_name, param in input_comp.vars.items(): assert param_name in output_comp.vars if isinstance(param, Expression): assert sorted([str(p) for p in param._params]) == sorted([str(p) for p in output_comp.vars[param_name]._params]) elif isinstance(param, Parameter): other_param = output_comp.vars[param_name] assert param.name == other_param.name assert param.fixed == other_param.fixed assert param.defined == other_param.defined assert param.max == other_param.max assert param.min == other_param.min assert param.is_periodic == other_param.is_periodic if param.defined: assert float(param) == float(other_param) if isinstance(input_comp, AFFConfigurator): assert input_comp.name == output_comp.name assert input_comp._blocked_circuit_size == output_comp._blocked_circuit_size assert input_comp._offset == output_comp._offset assert_circuits_eq(input_comp.default_circuit, output_comp.default_circuit) if isinstance(input_comp, FFConfigurator): assert input_comp._configs == output_comp._configs elif isinstance(input_comp, FFCircuitProvider): for state, coe in input_comp._map.items(): assert state in output_comp._map if isinstance(coe, ACircuit): assert_circuits_eq(coe, output_comp._map[state]) else: assert_experiment_equals(coe, output_comp._map[state]) elif isinstance(input_comp, IDetector): assert_detector_eq(input_comp, output_comp) elif isinstance(input_comp, PS) or not isinstance(input_comp, ACircuit): assert input_comp.describe() == output_comp.describe() elif isinstance(input_comp, CompiledCircuit): assert_compiled_circuit_equals(input_comp, output_comp) elif input_comp.defined and output_comp.defined: assert (input_comp.compute_unitary() == output_comp.compute_unitary()).all() else: assert input_comp.U == output_comp.U def assert_detector_list_eq(detect_a: list[IDetector], detect_b: list[IDetector]): assert len(detect_a) == len(detect_b) for da, db in zip(detect_a, detect_b): assert isinstance(da, type(db)) if da is not None: assert_detector_eq(da, db) def assert_detector_eq(left_detector: IDetector, right_detector: IDetector): assert left_detector.name == right_detector.name if isinstance(left_detector, BSLayeredPPNR): assert left_detector._r == right_detector._r assert left_detector._layers == right_detector._layers elif isinstance(left_detector, Detector): assert left_detector.max_detections == right_detector.max_detections assert left_detector._wires == right_detector._wires def assert_experiment_equals(experiment1: Experiment, experiment2: Experiment): assert experiment1.m == experiment2.m assert experiment1.name == experiment2.name assert experiment1.noise == experiment2.noise assert experiment1.input_state == experiment2.input_state assert experiment1.min_photons_filter == experiment2.min_photons_filter assert_component_list_eq(experiment1.components, experiment2.components) assert experiment1.in_port_names == experiment2.in_port_names assert experiment1.out_port_names == experiment2.out_port_names assert experiment1.post_select_fn == experiment2.post_select_fn assert_detector_list_eq(experiment1.detectors, experiment2.detectors) assert experiment1.heralds == experiment2.heralds assert experiment1.is_unitary == experiment2.is_unitary assert experiment1.has_td == experiment2.has_td assert experiment1.has_feedforward == experiment2.has_feedforward def assert_compiled_circuit_equals(cc_1: CompiledCircuit, cc_2: CompiledCircuit): assert cc_1.m == cc_2.m assert cc_1.name == cc_2.name assert cc_1.parameters == cc_2.parameters assert cc_1.version == cc_2.version def dict2svd(d: dict): return SVDistribution({StateVector(BasicState(k)): v for k, v in d.items()}) @pytest.fixture(scope="session") def save_figs(pytestconfig): return pytestconfig.getoption("save_figs") def _norm(svg): svg = svg.replace(" \n", "\n") svg = re.sub(r'url\(#.*?\)', 'url(#staticClipPath)', svg) svg = re.sub(r'', '', svg) svg = re.sub(r'(.*)', '', svg) svg = re.sub(r'(.*)', '', svg) return svg def _check_svg(test_path, ref_path, collection: str): with open(test_path) as f_test: test = _norm("".join(f_test.readlines())) with open(ref_path) as f_ref: ref = _norm("".join(f_ref.readlines())) m_test = re.search(rf'((.|\n)*?)', ref) if not m_test: return False, "cannot find patch in test" if not m_ref: return False, "cannot find patch in ref" m_test_patch = re.sub(r'url\(#.*?\)', "url()", m_test.group(1)) m_ref_patch = re.sub(r'url\(#.*?\)', "url()", m_ref.group(1)) if m_test_patch != m_ref_patch: return False, "test and ref are different" return True, "ok" def _check_circuit(test_path, ref_path): return _check_svg(test_path, ref_path, 'Patch') def _check_qpt(test_path, ref_path): return _check_svg(test_path, ref_path, 'Line3D') def _save_or_check(c, tmp_path, circuit_name, save_figs, recursive=False, compact=False, skin_type: type[ASkin] = PhysSkin) -> None: img_path = (TEST_IMG_DIR if save_figs else tmp_path) / \ Path(circuit_name + ".svg") skin = skin_type(compact) if isinstance(c, AProcessTomography): pdisplay_to_file(c, img_path, output_format=Format.MPLOT) elif isinstance(c, (AComponent, AProcessor, Experiment)): pdisplay_to_file(c, img_path, output_format=Format.MPLOT, recursive=recursive, skin=skin) else: raise NotImplementedError(f"_save_or_check not implemented for {type(c)}") if save_figs: with open(img_path) as f_saved: saved = "".join(f_saved.readlines()) saved = _norm(saved) with open(img_path, "w") as fw_saved: fw_saved.write(saved) else: if isinstance(c, AProcessTomography): ok, msg = _check_qpt(img_path, TEST_IMG_DIR / Path(circuit_name + ".svg")) elif isinstance(c, (AComponent, AProcessor, Experiment)): ok, msg = _check_circuit(img_path, TEST_IMG_DIR / Path(circuit_name + ".svg")) else: raise NotImplementedError(f"_save_or_check not implemented for {type(c)}") assert ok, msg class LogChecker: """Check if the logger as the log the expected number of log. The syntax to use this class is: with LogChecker(mock): do_something_that_expect_log It is inspired of the method pytest.warns() :param mock_warn: Mock object of the logger method to check (can be logger.warn, .info, .err ...) :param log_channel: Channel of the log to check, defaults to channel.user :param expected_log_number: Number of log expected to check, defaults to 1 """ def __init__(self, mock_warn: MagicMock, log_channel: channel = channel.user, expected_log_number: int = 1): self._mock_warn = mock_warn self._call_count = mock_warn.call_count self._expected_channel = log_channel self._expected_log_number = expected_log_number def __enter__(self): """Begging of the with statement. Check that no message has been log since last with statement. :return: self """ assert self._mock_warn.call_count == self._call_count return self def __exit__(self, exc_type, exc_val, exc_tb): """End of the with statement Check within the with statement that: - expected_log_number message(s) have been log/ Then for each messages that: - first argument is a str - second argument is the expected log channel :param exc_type: _ :param exc_val: _ :param exc_tb: _ """ self._call_count += self._expected_log_number assert self._mock_warn.call_count == self._call_count, f"logger.warn: expected {self._call_count} log but got {self._mock_warn.call_count}" for i in range(self._expected_log_number): assert isinstance(self._mock_warn.mock_calls[-i].args[0], str) assert self._mock_warn.mock_calls[-i].args[1] == self._expected_channel def set_expected_log_number(self, expected_log_number: int): """Setter of how many log message are expected :param expected_log_number: expected log number """ self._expected_log_number = expected_log_number def retry(exception_to_check: type[Exception], tries: int = 4, delay: float = 0, backoff: float = 1, logger=None): """Retry calling the decorated function using an exponential backoff. http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/ original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry :param exception_to_check: the exception type(s) to check; may be a tuple of types :param tries: number of tries (not retries) before giving up :param delay: initial delay between retries in seconds :param backoff: backoff multiplier e.g. value of 2 will double the delay each retry :param logger: logger to use. If None, send a warning """ def deco_retry(f): @wraps(f) def f_retry(*args, **kwargs): mtries, mdelay = tries, delay while mtries > 1: try: return f(*args, **kwargs) except exception_to_check as e: msg = f"{e}, Retrying in {mdelay} seconds..." if logger: logger.warning(msg) else: warnings.warn(msg) time.sleep(mdelay) mtries -= 1 mdelay *= backoff return f(*args, **kwargs) return f_retry # true decorator return deco_retry ================================================ FILE: tests/algorithm/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/algorithm/test_analyzer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import perceval as pcvl import perceval.components.unitary_components as comp import perceval.algorithm as algo from perceval.rendering.pdisplay import pdisplay_analyzer import sympy as sp import pytest from .._test_utils import strip_line_12 @pytest.mark.long_test def test_analyzer_on_qrng(): chip_QRNG = pcvl.Circuit(4, name='QRNG') # Parameters phis = [pcvl.Parameter("phi1"), pcvl.Parameter("phi2"), pcvl.Parameter("phi3"), pcvl.Parameter("phi4")] # Defining the LO elements of chip (chip_QRNG .add((0, 1), comp.BS()) .add((2, 3), comp.BS()) .add((1, 2), comp.PERM([1, 0])) .add(0, comp.PS(phis[0])) .add(2, comp.PS(phis[2])) .add((0, 1), comp.BS()) .add((2, 3), comp.BS()) .add(0, comp.PS(phis[1])) .add(2, comp.PS(phis[3])) .add((0, 1), comp.BS()) .add((2, 3), comp.BS()) ) # Setting parameters value and see how chip specs evolve phis[0].set_value(sp.pi/2) phis[1].set_value(0.2) phis[2].set_value(0) phis[3].set_value(0.4) p = pcvl.Processor("Naive", chip_QRNG) output_states = [ # Fix the output order for the unit test pcvl.FockState('|1,0,1,0>'), pcvl.FockState('|1,1,0,0>'), pcvl.FockState('|0,2,0,0>'), pcvl.FockState('|2,0,0,0>'), pcvl.FockState('|1,0,0,1>'), pcvl.FockState('|0,1,1,0>'), pcvl.FockState('|0,1,0,1>'), pcvl.FockState('|0,0,2,0>'), pcvl.FockState('|0,0,1,1>'), pcvl.FockState('|0,0,0,2>') ] ca = algo.Analyzer(p, [pcvl.FockState([1,0,1,0]), pcvl.FockState([0,1,1,0])], output_states) ca.compute() assert strip_line_12(pdisplay_analyzer(ca)) == strip_line_12(""" +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+ | | |1,0,1,0> | |1,1,0,0> | |0,2,0,0> | |2,0,0,0> | |1,0,0,1> | |0,1,1,0> | |0,1,0,1> | |0,0,2,0> | |0,0,1,1> | |0,0,0,2> | +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+ | |1,0,1,0> | 0.012162 | 0.240133 | 0.004934 | 0.004934 | 0.237838 | 0.237838 | 0.012162 | 0.018956 | 0.212088 | 0.018956 | | |0,1,1,0> | 0.012162 | 0.240133 | 0.004934 | 0.004934 | 0.237838 | 0.237838 | 0.012162 | 0.018956 | 0.212088 | 0.018956 | +-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+ """).strip() def test_analyzer_bs_1(): p = pcvl.Processor("Naive", comp.BS()) ca = algo.Analyzer(p, [pcvl.FockState([2,0])], [pcvl.FockState([1,1]), pcvl.FockState([2,0]), pcvl.FockState([0,2])]) ca.compute() assert ca.distribution[0, 0] == pytest.approx(1/2) # |1,1> assert ca.distribution[0, 1] == pytest.approx(1/4) # |2,0> assert ca.distribution[0, 2] == pytest.approx(1/4) # |0,2> def test_analyzer_bs_2(): bs = comp.BS() for backend_name in ["SLOS", "Naive"]: p = pcvl.Processor(backend_name, bs) ca = algo.Analyzer(p, [pcvl.FockState([0, 1]), pcvl.FockState([1, 0])]) ca.compute() assert pdisplay_analyzer(ca, nsimplify=True) == strip_line_12(""" +-------+-------+-------+ | | |0,1> | |1,0> | +-------+-------+-------+ | |0,1> | 1/2 | 1/2 | | |1,0> | 1/2 | 1/2 | +-------+-------+-------+""") assert pdisplay_analyzer(ca, nsimplify=False) == strip_line_12(""" +-------+-------+-------+ | | |0,1> | |1,0> | +-------+-------+-------+ | |0,1> | 0.5 | 0.5 | | |1,0> | 0.5 | 0.5 | +-------+-------+-------+""") ================================================ FILE: tests/algorithm/test_sampler.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval import NoiseModel, Detector from perceval.algorithm.sampler import Sampler import perceval as pcvl from perceval.components import BS, PS, Processor, catalog # To speed up the tests, lower the sample count required to compute a probability distribution Sampler.PROBS_SIMU_SAMPLE_COUNT = 1000 @pytest.mark.parametrize("backend_name", ["SLOS", "CliffordClifford2017"]) # MPS cannot be used with >2-modes components def test_sampler_standard(backend_name): TRANSMITTANCE = 0.9 noise_model = NoiseModel(brightness=TRANSMITTANCE) p = catalog['postprocessed cnot'].build_processor(backend=backend_name) p.noise = noise_model p.min_detected_photons_filter(0) p.with_input(pcvl.BasicState([1, 0, 1, 0])) sampler = Sampler(p) probs = sampler.probs() assert probs['results'][pcvl.BasicState([1, 0, 1, 0])] == pytest.approx(1) assert probs['results'][pcvl.BasicState([1, 0, 0, 1])] == pytest.approx(0) samples = sampler.samples(4) assert len(samples['results']) == 4 sample_count = sampler.sample_count(100) assert 90 < sum(list(sample_count['results'].values())) < 110 def test_sampler_missing_input_state(): p = Processor("SLOS", BS()) sampler = Sampler(p) with pytest.raises(AssertionError): sampler.probs.execute_async() with pytest.raises(AssertionError): sampler.sample_count.execute_async(100) with pytest.raises(AssertionError): sampler.samples.execute_async(100) p.with_input(pcvl.BasicState([1, 1])) # When setting a valid input state, the simulation works assert isinstance(sampler.probs()['results'], pcvl.BSDistribution) def test_sampler_iteration_missing_input_state(): sampler = Sampler(Processor("SLOS", BS(pcvl.P("theta0")))) sampler.add_iteration_list([{"circuit_params": {"theta0": x}} for x in range(0, 4)]) with pytest.raises(AssertionError): sampler.probs.execute_async() sampler = Sampler(Processor("SLOS", BS(pcvl.P("theta0")))) sampler.add_iteration_list([{"circuit_params": {"theta0": 0}, "input_state": pcvl.BasicState([1, 1])}, {"circuit_params": {"theta0": 1}, "input_state": pcvl.BasicState([1, 1])}, {"circuit_params": {"theta0": 1}} # input state is missing ]) with pytest.raises(AssertionError): sampler.probs.execute_async() def test_sampler_iteration_bad_params(): c = BS() // PS(phi=pcvl.P("phi1")) // BS() # circuit with only one variable parameter: phi1 p = Processor("SLOS", c) sampler = Sampler(p) with pytest.raises(AssertionError): sampler.add_iteration(circuit_params="phi0") # wrong parameter type with pytest.raises(AssertionError): sampler.add_iteration(circuit_params={"phi0": 1}) # phi0 doesn't exist in the input circuit with pytest.raises(AssertionError): sampler.add_iteration(circuit_params={"phi0": 1, "phi1": 2}) # phi0 doesn't exist in the input circuit with pytest.raises(AssertionError): sampler.add_iteration(circuit_params={"phi1": "phi"}) # phi is not an acceptable value with pytest.raises(AssertionError): sampler.add_iteration(input_state=pcvl.BasicState([1])) # input state too short with pytest.raises(AssertionError): sampler.add_iteration(input_state=pcvl.BasicState([1, 0, 0])) # input state too large def test_sampler_clear_iterations(): c = BS() // PS(phi=pcvl.P("phi1")) // BS() p = pcvl.Processor("SLOS", c) sampler = Sampler(p) iteration_list = [ {"circuit_params": {"phi1": 0.5}, "input_state": pcvl.BasicState([1, 1]), "min_detected_photons": 1}, {"circuit_params": {"phi1": 0.9}, "input_state": pcvl.BasicState([1, 1]), "min_detected_photons": 1}, {"circuit_params": {"phi1": 1.57}, "input_state": pcvl.BasicState([1, 0]), "min_detected_photons": 1} ] assert sampler.n_iterations == 0 sampler.add_iteration_list(iteration_list) assert sampler.n_iterations == len(iteration_list) sampler.add_iteration_list(iteration_list) assert sampler.n_iterations == len(iteration_list)*2 sampler.clear_iterations() assert sampler.n_iterations == 0 sampler.add_iteration(circuit_params={"phi1": 0.1}, input_state=pcvl.BasicState([0, 1])) assert sampler.n_iterations == 1 @pytest.mark.parametrize("backend_name", ["SLOS", "CliffordClifford2017"]) def test_sampler_iterator(backend_name): phi = pcvl.P("phi1") c = BS() // PS(phi=phi) // BS() phi.set_value(0) p = pcvl.Processor(backend_name, c, noise=pcvl.NoiseModel(.5)) p.min_detected_photons_filter(2) p.with_input(pcvl.BasicState([1, 1])) p.compute_physical_logical_perf(True) sampler = Sampler(p) iteration_list = [ {"circuit_params": {"phi1": 0.5}, "input_state": pcvl.BasicState([1, 1]), "min_detected_photons": 1}, {"circuit_params": {"phi1": 0.9}, "input_state": pcvl.BasicState([1, 1]), "max_samples": 20}, {"circuit_params": {"phi1": 1.57}, "input_state": pcvl.BasicState([1, 0]), "min_detected_photons": 1, "max_shots": 30}, {"circuit_params": {"phi1": 1.57}, "input_state": pcvl.BasicState([1, 0]), "min_detected_photons": 1, "noise": pcvl.NoiseModel()}, {} # Test default parameters ] sampler.add_iteration_list(iteration_list) rl = sampler.probs()['results_list'] assert len(rl) == len(iteration_list) for i in range(len(iteration_list)): assert "results" in rl[i] assert "iteration" in rl[i] assert rl[i]["iteration"] == iteration_list[i] # Test that the results changes given the iteration parameters (avoid Clifford as sampling adds randomness) if backend_name == "SLOS": assert len(rl[0]["results"]) == 5 # |0, 1>, |1, 0>, |2, 0>, |1, 1> and |0, 2> assert rl[1]["results"][pcvl.BasicState([1, 1])] == pytest.approx(0.38639895265345636) assert len(rl[2]["results"]) == 2 # |0, 1> and |1, 0> assert rl[2]["physical_perf"] == pytest.approx(.5) assert rl[4]["physical_perf"] == pytest.approx(.25) assert rl[3]["physical_perf"] == pytest.approx(1.) assert rl[4]["results"][pcvl.BasicState([1, 1])] == pytest.approx(1.) res = sampler.samples(max_samples=10) assert len(res['results_list']) == len(iteration_list) assert len(res['results_list'][0]["results"]) == 10 assert len(res['results_list'][1]["results"]) == 20 assert len(res['results_list'][2]["results"]) == 10 assert rl[3]["physical_perf"] == pytest.approx(1.) assert len(res['results_list'][4]["results"]) == 10 res = sampler.sample_count(max_samples=100) assert len(res['results_list']) == len(iteration_list) assert sum(res['results_list'][0]["results"].values()) == 100 assert sum(res['results_list'][1]["results"].values()) == 20 assert sum(res['results_list'][2]["results"].values()) == 30 assert rl[3]["physical_perf"] == pytest.approx(1.) assert sum(res['results_list'][4]["results"].values()) == 100 # Test wrong parameters if backend_name == "SLOS": # No need to do it twice n_it = sampler.n_iterations with pytest.raises(NotImplementedError): sampler.add_iteration(not_implemented_param = 0) with pytest.raises(AssertionError): sampler.add_iteration(input_state=[1, 0]) # Wrong type with pytest.raises(AssertionError): sampler.add_iteration(input_state=pcvl.BasicState([1])) # Wrong number of modes with pytest.raises(AssertionError): sampler.add_iteration(circuit_params={"phi0" : 1}) # Non-existing parameter with pytest.raises(AssertionError): sampler.add_iteration(circuit_params={"phi1" : "phi"}) # Not a number assert sampler.n_iterations == n_it # No new iteration def test_iterator_with_heralds(): c = pcvl.catalog['postprocessed cnot'].build_processor() processor = pcvl.Processor("SLOS") processor.add(0, c) processor.with_input(pcvl.BasicState([0, 1, 0, 1])) processor.min_detected_photons_filter(1) sampler = Sampler(processor, max_shots_per_call=500) sampler.add_iteration(input_state=pcvl.BasicState([1, 0, 1, 0])) sampler.add_iteration(input_state=pcvl.BasicState([0, 1, 0, 1])) sampler.add_iteration(input_state=pcvl.BasicState([0, 1, 1, 0])) sampler.add_iteration(input_state=pcvl.BasicState([1, 0, 0, 1])) res = sampler.probs()['results_list'] assert res[0]["results"][pcvl.BasicState([1, 0, 1, 0])] == pytest.approx(1.) assert res[1]["results"][pcvl.BasicState([0, 1, 1, 0])] == pytest.approx(1.) assert res[2]["results"][pcvl.BasicState([0, 1, 0, 1])] == pytest.approx(1.) assert res[3]["results"][pcvl.BasicState([1, 0, 0, 1])] == pytest.approx(1.) @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "CliffordClifford2017"]) def test_sampler_shots(backend_name): p = Processor(backend_name, BS(theta=0.8)) p.with_input(pcvl.BasicState([1, 1])) for m in range(p.circuit_size): p.add(m, Detector.threshold()) sampler = Sampler(p) # Without a max_shots_per_call value samples = sampler.samples(max_samples=100) assert len(samples['results']) == 100 # You get the number of samples you asked for sampler = Sampler(p, max_shots_per_call=10) samples = sampler.samples(max_samples=100) assert len(samples['results']) <= 10 samples = sampler.samples() assert len(samples['results']) <= 10 ================================================ FILE: tests/algorithm/test_tomography.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sys from pathlib import Path import pytest import numpy as np from scipy.stats import unitary_group import perceval as pcvl from perceval.components import (catalog, Processor, Circuit, PauliType, PauliEigenStateType, get_pauli_eigen_state_prep_circ) from perceval.backends import SLOSBackend from perceval.components import Unitary from perceval.algorithm import ProcessTomography, StateTomography from perceval.algorithm.tomography.tomography_utils import (is_physical, _generate_pauli_index, _vector_to_sq_matrix, _matrix_to_vector, _matrix_basis, _coef_linear_decomp, process_fidelity) CNOT_TARGET = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]], dtype=np.cdouble) @pytest.mark.parametrize("pauli_gate", [PauliEigenStateType.Zm, PauliEigenStateType.Zp, PauliEigenStateType.Xp, PauliEigenStateType.Yp]) def test_density_matrix_state_tomography(pauli_gate): p = Processor("Naive", Circuit(2) // get_pauli_eigen_state_prep_circ(pauli_gate)) # 1 qubit Pauli X gate qst = StateTomography(operator_processor=p) density_matrix = qst.perform_state_tomography([PauliEigenStateType.Xp]) res = is_physical(density_matrix, nqubit=1) assert len(density_matrix) == 2 assert res['Trace=1'] is True assert res["Hermitian"] is True assert res["Completely Positive"] is True def fidelity_op_process_tomography(op, op_proc): # create process tomography object qpt = ProcessTomography(operator_processor=op_proc) # compute Chi matrix chi_op_ideal = qpt.chi_target(op) chi_op = qpt.chi_matrix() # Compute fidelity op_fidelity = process_fidelity(chi_op, chi_op_ideal) return op_fidelity @pytest.mark.long_test def test_fidelity_klm_cnot(): # set operator circuit, num qubits cnot_p = catalog["klm cnot"].build_processor() cnot_fidelity = fidelity_op_process_tomography(CNOT_TARGET, cnot_p) assert cnot_fidelity == pytest.approx(1) @pytest.mark.long_test def test_fidelity_postprocessed_cnot(): # set operator circuit, num qubits cnot_p = catalog["postprocessed cnot"].build_processor() cnot_fidelity = fidelity_op_process_tomography(CNOT_TARGET, cnot_p) assert cnot_fidelity == pytest.approx(1) @pytest.mark.long_test def test_fidelity_random_op(): # process tomography to compute fidelity of a random 2 qubit gate operation nqubit = 2 L = [] for i in range(nqubit): L.append(unitary_group.rvs(2)) random_op = L[0] random_op_circ = pcvl.Circuit(2 * nqubit).add(0, Unitary(pcvl.Matrix(L[0]))) for i in range(1, nqubit): random_op = np.kron(random_op, L[i]) random_op_circ.add(2 * i, Unitary(pcvl.Matrix(L[i]))) random_op_proc = Processor(backend=SLOSBackend(), m_circuit=random_op_circ.m) random_op_proc.add(0, random_op_circ) random_op_fidelity = fidelity_op_process_tomography(random_op, random_op_proc) assert random_op_fidelity == pytest.approx(1) def test_processor_odd_modes(): # tests that a generic processor with odd number of modes does not work with pytest.raises(ValueError): ProcessTomography(operator_processor=Processor(SLOSBackend(), m_circuit=5)) @pytest.mark.long_test def test_chi_cnot_is_physical(): cnot_p = catalog["klm cnot"].build_processor() qpt = ProcessTomography(operator_processor=cnot_p) chi_op = qpt.chi_matrix() res = is_physical(chi_op, nqubit=2) assert res['Trace=1'] is True # if Chi has Trace = 1 assert res['Hermitian'] is True # if Chi is Hermitian assert res['Completely Positive'] is True # if input Chi is Completely Positive def test_pauli_order(): assert PauliType.I.value == 0 and PauliType.X.value == 1 and PauliType.Y.value == 2 and PauliType.Z.value == 3 def test_generate_pauli(): pauli_idx = _generate_pauli_index(2) for elem in pauli_idx: assert all(isinstance(val, PauliType) for val in elem) def test_utils_vector_to_sq_matrix(): # tests a valid vector that can be reshaped into a square matrix m = _vector_to_sq_matrix(np.ones(16)) assert m.shape == (4, 4) # tests an incorrect input to reshape to square matrix with pytest.raises(ValueError): _vector_to_sq_matrix(np.ones(10)) def test_matrix_to_vector(): v = _matrix_to_vector(np.eye(5)) assert len(v) == 25 def test_matrix_basis_n_decomp(): # generate matrix basis for the case with nqubit=1 basis = _matrix_basis(nqubit=1, d=2) # any random matrix of the corresponding size matrix = pcvl.MatrixN(np.random.randint(1, 10, (2, 2))) # decomposing matrix into basis mu = _coef_linear_decomp(matrix, basis) # reconstruct matrix_rebuilt = pcvl.MatrixN(np.zeros(matrix.shape, dtype=complex)) for idx, basis_matrices in enumerate(basis): matrix_rebuilt += mu[idx]*basis_matrices assert np.allclose(matrix, matrix_rebuilt) @pytest.mark.skip(reason='3 qubit tests takes a long time to compute') def test_avg_fidelity_postprocessed_ccz_gate(): ccz_p = catalog["postprocessed ccz"].build_processor() op_CCZ = np.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, -1]], dtype=np.cdouble) qpt = ProcessTomography(operator_processor=ccz_p) ccz_avg_f = qpt.average_fidelity(op_CCZ) assert ccz_avg_f == pytest.approx(1) ================================================ FILE: tests/algorithm/test_tomography_mle.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import sys from pathlib import Path import pytest import numpy as np from perceval.components import catalog, Processor, BS from perceval.algorithm import ProcessTomographyMLE, StateTomographyMLE from perceval.algorithm.tomography.tomography_utils import process_fidelity, is_physical CNOT_TARGET = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]], dtype=np.cdouble) GHZ_TARGET = np.zeros((8, 8)) GHZ_TARGET[0, 0], GHZ_TARGET[0, -1], GHZ_TARGET[-1, 0], GHZ_TARGET[-1, -1] = 1, 1, 1, 1 GHZ_TARGET /= 2 def fidelity_op_mle_process_tomography(op_proc): # create mle process tomography object qpt_mle = ProcessTomographyMLE(operator_processor=op_proc) chi_op = qpt_mle.chi_matrix() chi_op_ideal = qpt_mle.chi_target(CNOT_TARGET) op_fidelity = process_fidelity(chi_op, chi_op_ideal) return op_fidelity @pytest.mark.long_test def test_fidelity_heralded_cnot(): cnot_p = catalog["heralded cnot"].build_processor() cnot_fidelity_mle = fidelity_op_mle_process_tomography(cnot_p) assert cnot_fidelity_mle == pytest.approx(1) @pytest.mark.long_test def test_ghz_state_tomography_mle(): h_cnot_circ = catalog["klm cnot"].build_processor() ghz_state_proc = Processor("SLOS", 6) ghz_state_proc.add(0, BS.H()) ghz_state_proc.add(0, h_cnot_circ) ghz_state_proc.add(2, h_cnot_circ) s_mle = StateTomographyMLE(ghz_state_proc) ghz_state = s_mle.state_tomography_density_matrix() fidelity = s_mle.state_fidelity(GHZ_TARGET, ghz_state) assert np.trace(ghz_state) == pytest.approx(1) assert fidelity == pytest.approx(1) @pytest.mark.long_test def test_chi_cnot_from_mle_is_physical(): cnot_p = catalog["klm cnot"].build_processor() qpt = ProcessTomographyMLE(operator_processor=cnot_p) chi_op = qpt.chi_matrix() res = is_physical(chi_op, nqubit=2) assert res['Trace=1'] is True # if Chi has Trace = 1 assert res['Hermitian'] is True # if Chi is Hermitian assert res['Completely Positive'] is True # if input Chi is Completely Positive ================================================ FILE: tests/backends/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/backends/test_backends.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest from perceval.backends import Clifford2017Backend, AStrongSimulationBackend, SLOSBackend, BackendFactory, MPSBackend from perceval.components import BS, PS, PERM, Circuit, catalog from perceval.utils import BSCount, BasicState, Parameter, StateVector from .._test_utils import assert_sv_close def _assert_cnot(backend: AStrongSimulationBackend): s00 = BasicState([1, 0, 1, 0, 0, 0]) s01 = BasicState([1, 0, 0, 1, 0, 0]) s10 = BasicState([0, 1, 1, 0, 0, 0]) s11 = BasicState([0, 1, 0, 1, 0, 0]) backend.set_input_state(s00) assert pytest.approx(backend.probability(s00)) == 1 / 9 assert pytest.approx(backend.probability(s01)) == 0 backend.set_input_state(s01) assert pytest.approx(backend.probability(s01)) == 1 / 9 assert pytest.approx(backend.probability(s00)) == 0 backend.set_input_state(s10) assert pytest.approx(backend.probability(s11)) == 1 / 9 assert pytest.approx(backend.probability(s10)) == 0 backend.set_input_state(s11) assert pytest.approx(backend.probability(s11)) == 0 assert pytest.approx(backend.probability(s10)) == 1 / 9 def test_clifford_bs(): cliff_bs = Clifford2017Backend() cliff_bs.set_circuit(BS.H()) cliff_bs.set_input_state(BasicState([0, 1])) counts = BSCount() n_samples = 10000 for s in cliff_bs.samples(n_samples): counts[s] += 1 assert n_samples*0.475 < counts[BasicState("|0,1>")] < n_samples*0.525 assert n_samples*0.475 < counts[BasicState("|1,0>")] < n_samples*0.525 def check_output_distribution(backend: AStrongSimulationBackend, input_state: BasicState, expected: dict): backend.set_input_state(input_state) prob_list = [] for (output_state, prob) in backend.prob_distribution().items(): prob_expected = expected.get(output_state) if prob_expected is None: assert pytest.approx(0) == prob, "cannot find: %s (prob=%f)" % (str(output_state), prob) else: assert pytest.approx(prob_expected) == prob,\ "incorrect value for %s: %f/%f" % (str(output_state), prob, prob_expected) prob_list.append(prob) assert pytest.approx(sum(prob_list)) == 1 def test_backend_factory_default(): default_backend = BackendFactory.get_backend() default_backend.set_circuit(BS.H()) check_output_distribution(default_backend, BasicState([1, 0]), {BasicState("|1,0>"): 0.5, BasicState("|0,1>"): 0.5}) @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "NaiveApprox", "SLAP"]) def test_backend_wiring(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(Circuit(1)) # Identity circuit, 1 mode check_output_distribution(backend, BasicState([1]), {BasicState("|1>"): 1}) @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) def test_backend_identity(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(Circuit(2)) # Identity circuit, 2 modes check_output_distribution(backend, BasicState([0, 0]), {BasicState("|0,0>"): 1}) check_output_distribution(backend, BasicState([0, 1]), {BasicState("|0,1>"): 1}) check_output_distribution(backend, BasicState([1, 1]), {BasicState("|1,1>"): 1}) @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "CliffordClifford2017", "SLAP"]) def test_backend_wrong_size(backend_name): circuit = Circuit(2) state = BasicState([1, 1, 1]) backend = BackendFactory.get_backend(backend_name) with pytest.raises(AssertionError): backend.set_circuit(circuit) backend.set_input_state(state) @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) def test_backend_sym_bs(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(BS.H()) check_output_distribution(backend, BasicState("|2,0>"), {BasicState("|2,0>"): 0.25, BasicState("|1,1>"): 0.5, BasicState("|0,2>"): 0.25}) check_output_distribution(backend, BasicState("|1,0>"), {BasicState("|1,0>"): 0.5, BasicState("|0,1>"): 0.5}) check_output_distribution(backend, BasicState("|1,1>"), {BasicState("|2,0>"): 0.5, BasicState("|0,2>"): 0.5}) @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) def test_backend_asym_bs(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(BS.H(theta=2*math.pi/3)) check_output_distribution(backend, BasicState("|2,0>"), {BasicState("|2,0>"): 0.0625, BasicState("|1,1>"): 0.3750, BasicState("|0,2>"): 0.5625}) check_output_distribution(backend, BasicState("|1,0>"), {BasicState("|1,0>"): 0.25, BasicState("|0,1>"): 0.75}) def test_slos_precomputation(): """ Check if the SLOS backend is keeping internal structure""" slos = SLOSBackend() slos.set_circuit(Circuit(2)) slos.set_input_state(BasicState([0, 1])) assert len(slos._fsms) == 2 fsm_precompute_1 = slos._fsms[-1] slos.set_input_state(BasicState([1, 0])) assert len(slos._fsms) == 2 assert fsm_precompute_1 is slos._fsms[-1] slos.set_input_state(BasicState([1, 1])) assert len(slos._fsms) == 3 assert fsm_precompute_1 is slos._fsms[1] def test_slos_symbolic(): slos = SLOSBackend(use_symbolic=True) c = BS.H(theta=Parameter("theta")) slos.set_circuit(c) slos.set_input_state(BasicState([0, 1])) assert str(slos.probability(BasicState([0, 1]))) == "1.0*cos(theta/2)**2" slos.set_input_state(BasicState([1, 0])) assert str(slos.probability(BasicState([0, 1]))) == "1.0*sin(theta/2)**2" @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) def test_backend_cnot(backend_name): if backend_name == "MPS": # For MPS to be accurate enough, we need to increase the cutoff backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name, cutoff=4) else: backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) cnot = catalog["postprocessed cnot"].build_circuit() backend.set_circuit(cnot) _assert_cnot(backend) non_post_selected_probability = 0 backend.set_input_state(BasicState([1, 0, 1, 0, 0, 0])) # Two last modes are ancillaries for output_state, prob in backend.prob_distribution().items(): if output_state[4] or output_state[5]: non_post_selected_probability += prob assert pytest.approx(non_post_selected_probability) == 7/9 @pytest.mark.parametrize("backend_name", ["SLOS", "SLAP"]) def test_cnot_with_mask(backend_name): backend = BackendFactory.get_backend(backend_name) backend.set_mask([" 00"]) cnot = catalog["postprocessed cnot"].build_circuit() backend.set_circuit(cnot) _assert_cnot(backend) non_post_selected_probability = 0 backend.set_input_state(BasicState([0, 1, 0, 1, 0, 0])) for output_state, prob in backend.prob_distribution().items(): if output_state[4] or output_state[5]: non_post_selected_probability += prob assert pytest.approx(non_post_selected_probability) == 0 @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) def test_strong_sim_with_mask(backend_name): if backend_name == "MPS": # For MPS to be accurate enough, we need to increase the cutoff backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name, cutoff=4) else: backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_mask("****00") cnot = catalog["postprocessed cnot"].build_circuit() backend.set_circuit(cnot) logical00 = BasicState([1, 0, 1, 0, 0, 0]) backend.set_input_state(logical00) bsd = backend.prob_distribution() assert len(bsd) == 2 assert bsd[logical00] == pytest.approx(1 / 9) assert bsd[BasicState([1, 1, 0, 0, 0, 0])] == pytest.approx(1 / 9) @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) def test_probampli_backends(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) circuit = Circuit(3) // BS.H() // (1, PS(math.pi/4)) // (1, BS.H()) backend.set_circuit(circuit) check_output_distribution( backend, BasicState("|0,1,1>"), { BasicState("|0,1,1>"): 0, BasicState("|1,1,0>"): 0.25, BasicState("|1,0,1>"): 0.25, BasicState("|2,0,0>"): 0, BasicState("|0,2,0>"): 0.25, BasicState("|0,0,2>"): 0.25, }) backend.set_circuit(BS()) check_output_distribution( backend, BasicState("|2,3>"), { BasicState("|5,0>"): 0.3125, BasicState("|4,1>"): 0.0625, BasicState("|3,2>"): 0.125, BasicState("|2,3>"): 0.125, BasicState("|1,4>"): 0.0625, BasicState("|0,5>"): 0.3125, }) def test_slos_refresh_coefs(): """ The previous SLOS implementation was failing to refresh its results when the circuit was changed to another circuit of the same size, AFTER multiple fock states were used as input. The following code reproduces such a behavior """ slos = SLOSBackend() slos.set_circuit(BS()) # Use a beam splitter as circuit slos.set_input_state(BasicState("|1,1>")) slos.set_input_state(BasicState("|8,5>")) check_output_distribution( slos, BasicState("|1,1>"), # Input { # Expected results for a beam splitter BasicState("|0,2>"): 0.5, BasicState("|2,0>"): 0.5 }) slos.set_circuit(Circuit(2)) # Set the circuit as identity check_output_distribution( slos, BasicState("|1,1>"), { # Expected results for an identity BasicState("|1,1>"): 1 }) @pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) def test_evolve_indistinguishable(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(BS.H()) backend.set_input_state(BasicState([1, 0])) sv_out = backend.evolve() assert_sv_close(sv_out, math.sqrt(2)/2*StateVector([1, 0]) + math.sqrt(2)/2*StateVector([0, 1])) backend.set_input_state(BasicState([1, 1])) sv_out = backend.evolve() assert_sv_close(sv_out, math.sqrt(2)/2*StateVector([2, 0]) - math.sqrt(2)/2*StateVector([0, 2])) def test_backend_mps_n_mode_perm_decomp(): backend = MPSBackend() backend.set_circuit(Circuit(3) // (0, PERM([2, 0, 1])) // (1, BS.H())) for r, c in backend._circuit: if isinstance(c, PERM): assert c.m == 2 check_output_distribution(backend, BasicState("|2,0,0>"), {BasicState("|0,2,0>"): 0.25, BasicState("|0,1,1>"): 0.5, BasicState("|0,0,2>"): 0.25}) check_output_distribution(backend, BasicState("|1,0,0>"), {BasicState("|0,1,0>"): 0.5, BasicState("|0,0,1>"): 0.5}) check_output_distribution(backend, BasicState("|1,0,1>"), {BasicState("|0,2,0>"): 0.5, BasicState("|0,0,2>"): 0.5}) # For SLOS, the cached iterator is the largest layer of the compute path @pytest.mark.parametrize("backend_name", ["Naive", "MPS", "SLAP"]) def test_probampli_iterator_cache(backend_name): b: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) b.set_circuit(Circuit(5).add(0, BS.H())) b.set_input_state(BasicState([1, 1, 0, 0, 0])) b.evolve() assert len(b._cache_iterator) != 0 b.set_circuit(Circuit(7).add(3, BS.H())) assert len(b._cache_iterator) == 0 ================================================ FILE: tests/components/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/components/catalog/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/components/catalog/test_1_qubit_gates.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import math from perceval.components import catalog, PS, BS, PERM @pytest.mark.parametrize("gate_name, exptd_phi", [("s", math.pi / 2), ("sdag", -math.pi / 2), ("t", math.pi / 4), ("tdag", -math.pi / 4), ("ph", 0.0)]) def test_single_qubit_phase_gates(gate_name, exptd_phi): c = catalog[gate_name].build_circuit() assert c.m == 2 for _, comp in c._components: assert isinstance(comp, PS) assert comp.get_variables()['phi'] == exptd_phi % (2 * math.pi) def test_pauli_y_gate(): c = catalog["y"].build_circuit() assert len(c._components) == 3 assert isinstance(c._components[0][1], PERM) assert isinstance(c._components[1][1], PS) assert c._components[1][1].get_variables()['phi'] == math.pi/2 assert isinstance(c._components[2][1], PS) assert pytest.approx(c._components[2][1].get_variables()['phi']) == - math.pi / 2 % (2 * math.pi) def test_ry_gate(): c = catalog["ry"].build_circuit(theta=5*math.pi/2) assert isinstance(c._components[0][1], BS) assert c._components[0][1].get_variables()['theta'] == 5*math.pi/2 def test_rz_gate(): c = catalog["rz"].build_circuit(theta=math.pi) assert isinstance(c._components[0][1], PS) assert isinstance(c._components[1][1], PS) assert pytest.approx(c._components[0][1].get_variables()['phi']) == - math.pi / 2 % (2 * math.pi) assert c._components[1][1].get_variables()['phi'] == math.pi/2 def test_rx_gate(): c = catalog["rx"].build_circuit(theta=-math.pi/4) assert isinstance(c._components[0][1], BS) assert c._components[0][1].get_variables()['theta'] == math.pi/4 @pytest.mark.parametrize("gate_name, lo_comp", [('x', PERM), ('z', PS), ('h', BS)]) def test_single_qubit_gates(gate_name, lo_comp): c = catalog[gate_name].build_circuit() assert c.m == 2 for _, comp in c._components: assert isinstance(comp, lo_comp) ================================================ FILE: tests/components/catalog/test_2qbits_gates.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import cmath as cm import perceval as pcvl from perceval import BasicState, generate_all_logical_states, SimulatorFactory, Processor from perceval.components import catalog, BS, get_basic_state_from_ports from perceval.algorithm import Analyzer def test_fidelity_and_performance_cnot(): # Tests the performance and the fidelity of different CNOT in perceval ports = [pcvl.Port(pcvl.Encoding.DUAL_RAIL, "")] * 2 state_dict = {pcvl.components.get_basic_state_from_ports(ports, state): str( state) for state in pcvl.utils.generate_all_logical_states(2)} expected = {"|00>": "|00>", "|01>": "|01>", "|10>": "|11>", "|11>": "|10>"} # KLM CNOT analyzer_klm_cnot = Analyzer(catalog["klm cnot"].build_processor(), state_dict) analyzer_klm_cnot.compute(expected=expected) assert pytest.approx(analyzer_klm_cnot.fidelity) == 1 # Postprocessed CNOT analyzer_postprocessed_cnot = Analyzer( catalog["postprocessed cnot"].build_processor(), state_dict) analyzer_postprocessed_cnot.compute(expected=expected) assert pytest.approx(analyzer_postprocessed_cnot.fidelity) == 1 # CNOT using CZ : called - Heralded CNOT analyzer_heralded_cnot = Analyzer( catalog["heralded cnot"].build_processor(), state_dict) analyzer_heralded_cnot.compute(expected=expected) assert pytest.approx(analyzer_heralded_cnot.fidelity) == 1 assert analyzer_postprocessed_cnot.performance > analyzer_heralded_cnot.performance > analyzer_klm_cnot.performance def check_cz_with_heralds_or_ancillaries(processor, herald_states, error=1E-6): """Check if the cz is correct Meaning checking that the CZ gate probability amplitude matrix should be: ⎡r*exp(iθ) 0 0 0 ⎤ ⎢0 r*exp(iθ) 0 0 ⎥ ⎢0 0 r*exp(iθ) 0 ⎥ ⎣0 0 0 r*exp(i(θ+φ)) ⎦ With r the modulus (r!=0), θ the global phase and φ the rotation angle of our gate (for CZ is φ=π) :param processor: CZ processor to check, we assume that ctrl is on [0,1] and data on [2,3] :param herald_states: Basic state corresponding to the heralded or ancillary modes at the end of the circuit :param error: Tolerance for the modulus of the prob_amplitude (when not expecting 0), defaults to 1E-6 This param represent whether this cz gate is balanced """ ports = [pcvl.Port(pcvl.Encoding.DUAL_RAIL, "")] * 2 states = [get_basic_state_from_ports( ports, state) * herald_states for state in generate_all_logical_states(2)] sim = SimulatorFactory().build(processor) data_state = BasicState("|0,1,0,1>") * herald_states modulus_value = None phase_value = None for i_state in states: for o_state in states: pa = sim.prob_amplitude(i_state, o_state) modulus = abs(pa) phase = cm.phase(pa) if i_state == o_state: if modulus_value is None: modulus_value = modulus assert pytest.approx(modulus, error) == modulus_value assert modulus != 0 if i_state != data_state: if phase_value is None: phase_value = phase assert pytest.approx(phase) == phase_value else: assert pytest.approx(phase) == phase_value + cm.pi else: assert pytest.approx(modulus) == 0 return modulus_value def test_cz_and_cnot_phases_and_modulus(): # Testing phases and modulus of CCZ check_cz_with_heralds_or_ancillaries( catalog["heralded cz"].build_processor(), BasicState("|1,1>")) # Testing phases and modulus of heralded cnot by transforming it in a CZ gate with Hadamard gates processor = Processor("SLOS", 4) processor.add(2, BS.H()) processor.add(0, catalog["heralded cnot"].build_processor()) processor.add(2, BS.H()) check_cz_with_heralds_or_ancillaries(processor, BasicState("|1,1>")) # Testing phases and modulus of klm cnot by transforming it in a CZ gate with Hadamard gates processor = Processor("SLOS", 4) processor.add(2, BS.H()) processor.add(0, catalog["klm cnot"].build_processor()) processor.add(2, BS.H()) check_cz_with_heralds_or_ancillaries(processor, BasicState("|0,1,0,1>")) # Testing phases and modulus of postprocessed cnot by transforming it in a CZ gate with Hadamard gates processor = Processor("SLOS", 4) processor.add(2, BS.H()) processor.add(0, catalog["postprocessed cnot"].build_processor()) processor.add(2, BS.H()) check_cz_with_heralds_or_ancillaries(processor, BasicState("|0,0>")) @pytest.mark.skip(reason="redundant with overhead test") @pytest.mark.parametrize("cnot_gate", ["klm cnot", "postprocessed cnot", "heralded cnot"]) def test_inverted_cnot(cnot_gate): """Test cnot gate phase by inverting it with Hadamard gates ╭───╮ ╭──────────╮ ╭───╮ ╭──────────╮ ────┤ H ├───┤ DATA ├───┤ H ├──── ───┤ CTRL ├─── ────┤ ├───┤ ├───┤ ├──── ───┤ ├─── ╰───╯ │ CNOT │ ╰───╯ <=> │ CNOT │ ╭───╮ │ │ ╭───╮ │ │ ────┤ H ├───┤ CTRL ├───┤ H ├──── ───┤ DATA ├─── ────┤ ├───┤ ├───┤ ├──── ───┤ ├─── ╰───╯ ╰──────────╯ ╰───╯ ╰──────────╯ :param cnot_gate: cnot catalog gate """ processor = Processor("SLOS", 4) processor.add([0, 1], BS.H()) processor.add([2, 3], BS.H()) processor.add( [2, 3, 0, 1], catalog[cnot_gate].build_processor()) # >= 0.9.0 processor.add([0, 1], BS.H()) processor.add([2, 3], BS.H()) state_dict = {pcvl.components.get_basic_state_from_ports(processor._out_ports, state): str( state) for state in pcvl.utils.generate_all_logical_states(2)} analyzer = Analyzer(processor, state_dict) analyzer.compute(expected={"00": "00", "01": "01", "10": "11", "11": "10"}) if cnot_gate == "klm cnot": assert pytest.approx(analyzer.fidelity, 1E-4) == 1 elif cnot_gate == "heralded cnot": assert pytest.approx(analyzer.fidelity) == 1 else: assert analyzer.fidelity == 1 ================================================ FILE: tests/components/catalog/test_pauli.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import numpy as np from perceval.components import (PauliType, PauliEigenStateType, processor_circuit_configurator, catalog, Circuit, Processor, get_pauli_eigen_state_prep_circ, get_pauli_gate) def test_pauli_type(): pauli_s = list(PauliType) pauli_val = [member.value for member in PauliType] is_ascending = np.all(np.diff(pauli_val) >= 0) assert len(pauli_s) == 4 assert is_ascending == True @pytest.mark.parametrize("pauli_eigen_states", [item for item in PauliEigenStateType]) def test_pauli_state_prep_circuits(pauli_eigen_states): c = Circuit(2) // get_pauli_eigen_state_prep_circ(pauli_eigen_states) assert c.m == 2 @pytest.mark.parametrize("pauli_gate", [PauliType.X, PauliType.Y, PauliType.Z]) def test_pauli_gates(pauli_gate): gate_matrix = get_pauli_gate(pauli_gate) assert np.trace(gate_matrix) == 0 # verify pauli matrices are traceless def test_processor_circuit_configurator(): with pytest.raises(TypeError): processor_circuit_configurator(Circuit(2), [PauliEigenStateType.Zm, PauliEigenStateType.Zm], [PauliType.I, PauliType.I],) cnot = catalog["klm cnot"].build_processor() with pytest.raises(TypeError): processor_circuit_configurator(cnot, [1, 0],[1, 0]) configured_cnot = processor_circuit_configurator(cnot, [PauliEigenStateType.Zm, PauliEigenStateType.Zm], [PauliType.I, PauliType.I],) assert isinstance(configured_cnot, Processor) ================================================ FILE: tests/components/catalog/test_qloq.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval import PERM, Circuit, catalog from perceval.components.core_catalog._helpers.entanglement_qloq import generate_permutation_for_controlled_op, \ create_internal_controlled_op from perceval.components.core_catalog._helpers.rotations_qloq import internal_swap, G_RHn, G_RHk, _g_rk, _g_rn def test_internal_swap(): # Simple swap n_qubit = 2 swap = internal_swap(0, 1, n_qubit) assert swap.m == 2 ** n_qubit target = PERM([0, 2, 1, 3]) assert swap.compute_unitary() == pytest.approx(target.compute_unitary()) # Big swap n_qubit = 4 swap = internal_swap(1, 3, n_qubit) assert swap.m == 2 ** n_qubit # swap the 2nd and 4th qubit if mode i represents the qubit i written in binary (e.g. 3 --> "0011") target = PERM([0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15]) assert swap.compute_unitary() == pytest.approx(target.compute_unitary()) def test_rotations_qloq(): n_qubit = 2 angle = 1.23 mapping = {"X": catalog["rx"].build_circuit(theta=angle), "Y": catalog["ry"].build_circuit(theta=angle), "Z": catalog["rz"].build_circuit(theta=angle), "H": catalog["h"].build_circuit()} for gate, target in mapping.items(): # Rotations on last qubit if gate == "H": circ = G_RHn(n_qubit) else: circ = _g_rn(gate, angle, n_qubit) target_circuit = Circuit(2 ** n_qubit) for i in range(0, 2 ** n_qubit, 2): target_circuit //= (i, target) assert circ.compute_unitary() == pytest.approx(target_circuit.compute_unitary()) # Now, rotation on 0-th qubit if gate == "H": circ = G_RHk(n_qubit, 0) else: circ = _g_rk(angle, n_qubit, 0, gate) # Since swap is an involution, # applying the swap before and after the circuit should give the same as when we apply the gate on the last qubit circ = internal_swap(0, n_qubit - 1, n_qubit) // (0, circ) // (0, internal_swap(0, n_qubit - 1, n_qubit)) assert circ.compute_unitary() == pytest.approx(target_circuit.compute_unitary()) def test_internal_entanglement(): # Simple case n_qubit = 2 cnot_perm = generate_permutation_for_controlled_op(0, 1, n_qubit) assert cnot_perm == [0, 1, 3, 2] cnot_perm = generate_permutation_for_controlled_op(1, 0, n_qubit) assert cnot_perm == [0, 3, 2, 1] # Big cnot n_qubit = 4 control = 1 target = 3 cnot_perm = generate_permutation_for_controlled_op(control, target, n_qubit) assert cnot_perm == [0, 1, 2, 3, 5, 4, 7, 6, 8, 9, 10, 11, 13, 12, 15, 14] # Now as circuits cnot_circ = create_internal_controlled_op("CNOT", control, target, n_qubit) assert cnot_circ.compute_unitary() == pytest.approx(PERM(cnot_perm).compute_unitary()) cz_circ = create_internal_controlled_op("CZ", control, target, n_qubit) # Applies H gates to create a CNOT cz_circ = G_RHk(n_qubit, target) // (0, cz_circ) // (0, G_RHk(n_qubit, target)) assert cz_circ.compute_unitary() == pytest.approx(cnot_circ.compute_unitary()) ================================================ FILE: tests/components/test_circuit.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from pathlib import Path from collections import Counter from perceval import Circuit, P, BasicState, pdisplay, Matrix, BackendFactory, Processor from perceval.rendering.pdisplay import pdisplay_circuit, pdisplay_matrix from perceval.rendering.format import Format from perceval.utils import InterferometerShape import perceval.algorithm as algo import perceval.components.unitary_components as comp from .._test_utils import strip_line_12, assert_circuits_eq import sympy as sp import numpy as np def test_helloword(): c = comp.BS() assert c.m == 2 definition = c.definition() assert strip_line_12(pdisplay_matrix(definition)) == strip_line_12(""" ⎡exp(I*(phi_tl + phi_tr))*cos(theta/2) I*exp(I*(phi_bl + phi_tr))*sin(theta/2)⎤ ⎣I*exp(I*(phi_br + phi_tl))*sin(theta/2) exp(I*(phi_bl + phi_br))*cos(theta/2) ⎦ """) expected = [P(name='theta', value=sp.pi/2, min_v=0, max_v=4*sp.pi), P(name='phi_tl', value=0, min_v=0, max_v=2*sp.pi), P(name='phi_bl', value=0, min_v=0, max_v=2*sp.pi), P(name='phi_tr', value=0, min_v=0, max_v=2*sp.pi), P(name='phi_br', value=0, min_v=0, max_v=2*sp.pi), ] for p_res, p_exp in zip(c.get_parameters(True), expected): assert str(p_res) == str(p_exp) assert c.U.is_unitary() for backend_name in ["SLOS", "Naive"]: backend = BackendFactory.get_backend(backend_name) backend.set_circuit(c) expected_outputs = { BasicState("|0,1>"): 0.5, BasicState("|1,0>"): 0.5 } input_state = BasicState("|0,1>") backend.set_input_state(input_state) bsd = backend.prob_distribution() for output_state, p in bsd.items(): assert output_state in expected_outputs assert pytest.approx(expected_outputs[output_state]) == p assert pytest.approx(expected_outputs[output_state]) == backend.probability(output_state) assert len(bsd) == len(expected_outputs) p = Processor(backend_name, c) ca = algo.Analyzer(p, [BasicState([0, 1]), BasicState([1, 0]), BasicState([1, 1])], # the input states "*" # all possible output states that can be generated with 1 or 2 photons ) b01 = BasicState("|0,1>") b10 = BasicState("|1,0>") b20 = BasicState("|2,0>") b11 = BasicState("|1,1>") b02 = BasicState("|0,2>") # test if the input & output states are the same input_states = [b01, b11, b10] output_states = [b01, b10, b20, b11, b02] assert Counter(ca.input_states_list) == Counter(input_states) assert Counter(ca.output_states_list) == Counter(output_states) # test if it's this distribution: # +-------+-------+-------+-------+-------+-------+ # | | |0,1> | |1,0> | |2,0> | |1,1> | |0,2> | # +-------+-------+-------+-------+-------+-------+ # | |0,1> | 1/2 | 1/2 | 0 | 0 | 0 | # | |1,0> | 1/2 | 1/2 | 0 | 0 | 0 | # | |1,1> | 0 | 0 | 1/2 | 0 | 1/2 | # +-------+-------+-------+-------+-------+-------+ input_states_dict = {elem: i for i, elem in enumerate(ca.input_states_list)} output_states_dict = {elem: i for i, elem in enumerate(ca.output_states_list)} assert ca.distribution[input_states_dict[b01]][output_states_dict[b01]] == pytest.approx(1 / 2) assert ca.distribution[input_states_dict[b01]][output_states_dict[b10]] == pytest.approx(1 / 2) assert ca.distribution[input_states_dict[b01]][output_states_dict[b20]] == pytest.approx(0) assert ca.distribution[input_states_dict[b01]][output_states_dict[b11]] == pytest.approx(0) assert ca.distribution[input_states_dict[b01]][output_states_dict[b02]] == pytest.approx(0) assert ca.distribution[input_states_dict[b10]][output_states_dict[b01]] == pytest.approx(1 / 2) assert ca.distribution[input_states_dict[b10]][output_states_dict[b10]] == pytest.approx(1 / 2) assert ca.distribution[input_states_dict[b10]][output_states_dict[b20]] == pytest.approx(0) assert ca.distribution[input_states_dict[b10]][output_states_dict[b11]] == pytest.approx(0) assert ca.distribution[input_states_dict[b10]][output_states_dict[b02]] == pytest.approx(0) assert ca.distribution[input_states_dict[b11]][output_states_dict[b01]] == pytest.approx(0) assert ca.distribution[input_states_dict[b11]][output_states_dict[b10]] == pytest.approx(0) assert ca.distribution[input_states_dict[b11]][output_states_dict[b20]] == pytest.approx(1 / 2) assert ca.distribution[input_states_dict[b11]][output_states_dict[b11]] == pytest.approx(0) assert ca.distribution[input_states_dict[b11]][output_states_dict[b02]] == pytest.approx(1 / 2) def test_empty_circuit(): c = Circuit(4) m = c.compute_unitary() assert m.shape == (4, 4) assert np.allclose(m, Matrix.eye(4)) assert pdisplay_circuit(c).replace(" ", "") == """ 0:────:0 (depth 0) 1:────:1 (depth 0) 2:────:2 (depth 0) 3:────:3 (depth 0) """.replace(" ", "") def test_bs_symbolic_unitary(): phi_tl = P("phi") theta = P("theta") bs = comp.BS(theta=theta, phi_tl=phi_tl) assert strip_line_12(pdisplay_matrix(bs.compute_unitary(use_symbolic=True))) == strip_line_12(""" ⎡exp(I*phi)*cos(theta/2) I*sin(theta/2)⎤ ⎣I*exp(I*phi)*sin(theta/2) cos(theta/2) ⎦""") def test_bs_u(): bs = comp.BS() assert pdisplay_matrix(bs.U) == "⎡sqrt(2)/2 sqrt(2)*I/2⎤\n⎣sqrt(2)*I/2 sqrt(2)/2 ⎦" bs = comp.BS(theta=comp.BS.r_to_theta(1)) assert pdisplay_matrix(bs.U) == "⎡1 0⎤\n⎣0 1⎦" bs = comp.BS(theta=comp.BS.r_to_theta(0)) assert pdisplay_matrix(bs.U) == "⎡0 I⎤\n⎣I 0⎦" def test_symbolic_theta_in_beam_splitter(): theta = P("theta0") bs = comp.BS(theta=theta) with pytest.raises(AssertionError): # Cannot request a numeric matrix with variable parameters bs.compute_unitary(use_symbolic=False) assert pdisplay_matrix(bs.compute_unitary(use_symbolic=True)) == strip_line_12(""" ⎡cos(theta0/2) I*sin(theta0/2)⎤ ⎣I*sin(theta0/2) cos(theta0/2) ⎦""") def test_double_parameter_ok(): phi1 = P("phi") comp.BS(phi_tl=phi1, phi_bl=phi1) def test_double_parameter_dup(): phi1 = P("phi") phi2 = P("phi") with pytest.raises(RuntimeError): # Exception should be raised for two parameters with same name comp.BS(phi_tr=phi1, phi_bl=phi2) def test_double_parameter_dup_multi(): phi1 = P("phi") phi2 = P("phi") with pytest.raises(RuntimeError): # Exception should have been generated for two parameters with same name comp.BS(phi_tr=phi1) // comp.BS(phi_tl=phi2) def test_build_composition(): a = comp.BS() b = comp.BS() c = a // b assert pdisplay_matrix(c.U) == "⎡0 I⎤\n⎣I 0⎦" def test_build_composition_2(): c = comp.BS() // comp.PS(phi=sp.pi/2) assert pdisplay_matrix(c.U) == "⎡sqrt(2)*I/2 -sqrt(2)/2⎤\n⎣sqrt(2)*I/2 sqrt(2)/2 ⎦" def test_build_composition_3(): c = comp.BS() // (0, comp.PS(phi=sp.pi/2)) assert pdisplay_matrix(c.U) == "⎡sqrt(2)*I/2 -sqrt(2)/2⎤\n⎣sqrt(2)*I/2 sqrt(2)/2 ⎦" def test_build_composition_4(): c = comp.BS() // (1, comp.PS(phi=sp.pi/2)) assert pdisplay_matrix(c.U) == "⎡sqrt(2)/2 sqrt(2)*I/2⎤\n⎣-sqrt(2)/2 sqrt(2)*I/2⎦" def test_out_of_bound_composition(): with pytest.raises(AssertionError): comp.BS() // (1, comp.BS()) # Cannot add a 2-mode component on a 2-mode circuit, starting from mode 1 with pytest.raises(AssertionError): comp.BS().add(1, comp.BS()) def test_unitary_component(): non_unitary_matrix = Matrix([[1, 2], [3, 4]]) with pytest.raises(AssertionError): comp.Unitary(non_unitary_matrix) odd_size_matrix = Matrix.random_unitary(5) with pytest.raises(AssertionError): # In case the unitary component is polarized, the unitary matrix size must be even comp.Unitary(odd_size_matrix, use_polarization=True) unitary = comp.Unitary(odd_size_matrix) assert (unitary.U == odd_size_matrix).all() def test_unitary_inverse(): """ Testing vertical inversion can be performed by applying a m:-1:0 permutation on the right and on the left of the inverted circuit. The resulting circuit has to be equivalent to the input circuit. """ size = 4 perm_tester = comp.PERM(list(range(size-1, -1, -1))) input_component = comp.Unitary(Matrix.random_unitary(size)) inverted_component = comp.Unitary(input_component.U) inverted_component.inverse(v=True) test_circuit = Circuit(size)\ .add(0, perm_tester)\ .add(0, inverted_component)\ .add(0, perm_tester) assert np.array_equal(input_component.U, test_circuit.U) # Test v and h inversion interaction (the order should have no impact on the result) u1 = comp.Unitary(Matrix.random_unitary(size)) u2 = comp.Unitary(U=u1.U) u1.inverse(h=True) u1.inverse(v=True) u2.inverse(v=True) u2.inverse(h=True) assert np.allclose(u1.U, u2.U, atol=1e-12) def test_iterator(): c = Circuit(3) comps = [(0, 1), (1, 2), (0, 1)] for k in range(len(comps)): c.add(comps[k], comp.BS(theta=1/(k+1))) d = Circuit(4) d.add((0, 1, 2), c, merge=False) d.add((2, 3), comp.BS(theta=1/4)) comps.append((2, 3)) l_comp = list(d.__iter__()) assert len(l_comp) == 4 for i in range(4): assert float(l_comp[i][1].param("theta")) == 1/(i+1) and l_comp[i][0] == comps[i] def _generate_simple_circuit(): return (comp.Unitary(U=Matrix.random_unitary(3), name="U1") // (0, comp.PS(sp.pi / 2)) // comp.Unitary(U=Matrix.random_unitary(3), name="U2")) def test_visualization_ucircuit(capfd): c = _generate_simple_circuit() pdisplay(c, output_format=Format.TEXT) out, err = capfd.readouterr() assert out.strip() == """ ╭─────╮╭───────────╮╭─────╮ 0:──┤U1 ├┤PS phi=pi/2├┤U2 ├──:0 (depth 3) │ │╰───────────╯│ │ │ │ │ │ 1:──┤ ├─────────────┤ ├──:1 (depth 2) │ │ │ │ │ │ │ │ 2:──┤ ├─────────────┤ ├──:2 (depth 2) ╰─────╯ ╰─────╯ """.strip() def test_visualization_barrier(capfd): u = comp.Unitary(U=Matrix.random_unitary(2), name="U") c = Circuit(4) // u @ (2, u) // (1, u) @ u assert c[0, 1].describe() == "Barrier(4)" pdisplay(c, output_format=Format.TEXT) out, err = capfd.readouterr() ascii_representation = """ ╭─────╮ ║ ║ ╭─────╮\n""" +\ "0:──┤U ├──║──────────────────║──┤U ├──:0 (depth 2)\n" +\ " │ │ ║ ║ │ │\n" +\ " │ │ ║ ╭─────╮ ║ │ │\n" +\ "1:──┤ ├──║─────────┤U ├──║──┤ ├──:1 (depth 3)\n" +\ " ╰─────╯ ║ │ │ ║ ╰─────╯\n" +\ " ║ ╭─────╮│ │ ║ \n" +\ "2:───────────║──┤U ├┤ ├──║───────────:2 (depth 2)\n"+\ " ║ │ │╰─────╯ ║ \n"+\ " ║ │ │ ║ \n"+\ "3:───────────║──┤ ├─────────║───────────:3 (depth 1)\n"+\ " ║ ╰─────╯ ║ " assert out.strip() == ascii_representation.strip() TEST_DATA_DIR = Path(__file__).resolve().parent.parent / 'data' @pytest.mark.long_test def test_depths_ncomponents(): assert comp.PS(0).depths() == [1] assert comp.PS(0).ncomponents() == 1 c = _generate_simple_circuit() assert c.depths() == [3, 2, 2] assert c.ncomponents() == 3 with open(TEST_DATA_DIR / 'u_random_8', "r") as f: m = Matrix(f) ub = (Circuit(2) // comp.BS() // (0, comp.PS(phi=P("φ_a"))) // comp.BS() // (0, comp.PS(phi=P("φ_b")))) c1 = Circuit.decomposition(m, ub, shape=InterferometerShape.TRIANGLE) assert c1 is not None and c1.depths() == [28, 38, 32, 26, 20, 14, 8, 2] assert c1.ncomponents() == 112 def test_reflexivity(): c = comp.BS(theta=comp.BS.r_to_theta(1/3), convention=comp.BSConvention.H) assert pytest.approx(c.compute_unitary(use_symbolic=False)[0, 0]) == np.sqrt(1/3) def test_getitem1_index(): c = Circuit(2) // comp.BS() // comp.PS(P("phi1")) // comp.BS() // comp.PS(P("phi2")) with pytest.raises(IndexError): _ = c[0, 5] with pytest.raises(ValueError): _ = c[-1] with pytest.raises(IndexError): _ = c[4, 0] def test_getitem2_value(): c = Circuit(2) // comp.BS.H(theta=P("thet0")) // comp.PS(P("phi1")) // comp.BS.H() // comp.PS(P("phi2")) assert c[0, 0].describe() == "BS.H(theta=thet0)" assert c[0, 1].describe() == "PS(phi=phi1)" def test_getitem3_parameter(): c = Circuit(2) // comp.BS.H() // comp.PS(P("phi1")) // comp.BS.H() // comp.PS(P("phi2")) assert c.getitem((0, 0), True).describe() == "PS(phi=phi1)" def test_describe(): assert comp.BS().describe() == "BS.Rx()" assert comp.BS.Rx().describe() == "BS.Rx()" assert comp.BS.Ry().describe() == "BS.Ry()" assert comp.BS.H().describe() == "BS.H()" assert comp.BS.H(theta=1.07).describe() == "BS.H(theta=1.07)" assert comp.BS.H(theta=1.07, phi_br=0.2687).describe() == "BS.H(theta=1.07, phi_br=0.2687)" assert comp.BS.H(theta=P("my_var")).describe() == "BS.H(theta=my_var)" param = P("my_var2") param.set_value(1.097) assert comp.BS.H(theta=param).describe() == "BS.H(theta=1.097)" assert comp.PS(0).describe() == "PS(phi=0)" assert comp.PERM([1, 2, 3, 0]).describe() == "PERM([1, 2, 3, 0])" def test_copy(): # Test with numerical values c = Circuit(2) // comp.BS.H() // comp.PS(0.3) // comp.BS.H() // comp.PS(0.8) c_copy = c.copy() assert_circuits_eq(c, c_copy) assert c is not c_copy # Test with Parameters without values c = Circuit(2) // comp.BS.H() // comp.PS(P("phi1")) // comp.BS.H() // comp.PS(P("phi2")) c_copy = c.copy() assert_circuits_eq(c, c_copy) # Test with parameters with values c.param('phi1').set_value(0.4) c.param('phi2').set_value(0.6) c_copy = c.copy() assert_circuits_eq(c, c_copy) # Test with Expressions without values c = Circuit(2) // comp.BS.H() // comp.PS(P("phi1") + P("phi11")) // comp.BS.H() // comp.PS(P("phi2") * P("phi21")) c_copy = c.copy() assert_circuits_eq(c, c_copy) # Test with Expressions with values c = Circuit(2) // comp.BS.H() // comp.PS(P("phi1") + P("phi11")) // comp.BS.H() // comp.PS(P("phi2") * P("phi21")) c.param('phi1').set_value(0.4) c.param('phi11').set_value(0.5) c.param('phi2').set_value(0.6) c.param('phi21').set_value(0.7) c_copy = c.copy() assert_circuits_eq(c, c_copy) # Test with repeated Parameter phi = P("phi") c = Circuit(2) // comp.BS.H() // comp.PS(phi) // comp.BS.H() // comp.PS(phi) c_copy = c.copy() assert_circuits_eq(c, c_copy) assert len(c_copy.vars) == 1 assert next(iter(c_copy.vars)) is not phi # Check that the parameter has effectively been copied phi.set_value(0.5) assert not c_copy.defined, "Parameters have not been copied" c_copy = c.copy() assert_circuits_eq(c, c_copy) # All combined phi = P("phi") phi.set_value(0.5) phi2 = P("phi2") phi3 = P("phi3") phi3.set_value(1.2) c = (Circuit(2) // comp.BS.H(P("theta1")) // comp.PS(phi + phi3) // comp.BS.H(0.67) // comp.PS(phi2 * P("phi21")) // comp.BS.H(phi)) // comp.PS(phi2) c_copy = c.copy() assert_circuits_eq(c, c_copy) ================================================ FILE: tests/components/test_compiled_circuit.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np from packaging.version import Version from perceval.components.compiled_circuit import CompiledCircuit from perceval.components.core_catalog.mzi import MZIPhaseFirst from perceval.components.experiment import Experiment def test_inheritance(): circuit = CompiledCircuit("chip name", 2, [], Version("1.0")) exp = Experiment(3, None, "-") exp.add(1, circuit) exp = Experiment(circuit, None, "-") def test_compute_unitary(): circuit = CompiledCircuit("chip name", MZIPhaseFirst().build_circuit(), [0., 1.], Version("1.0")) assert np.allclose(circuit.compute_unitary(), MZIPhaseFirst().build_circuit(phi_a = 0, phi_b = 1.).compute_unitary()) ================================================ FILE: tests/components/test_compute_unitary.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import perceval as pcvl import perceval.components.unitary_components as comp import numpy as np def _check_unitary(component: pcvl.ACircuit): u_symb = component.compute_unitary(use_symbolic=True) u_num = component.compute_unitary(use_symbolic=False) assert u_symb.is_unitary() assert u_num.is_unitary() assert np.allclose(u_symb.tonp(), u_num) def test_BS_unitary(): bs = comp.BS(theta=0.43, phi_tl=0.26, phi_bl=1.6, phi_tr=0.04, phi_br=2.13) _check_unitary(bs) bs = comp.BS.H(theta=0.43, phi_tl=0.26, phi_bl=1.6, phi_tr=0.04, phi_br=2.13) _check_unitary(bs) bs = comp.BS.Ry(theta=0.43, phi_tl=0.26, phi_bl=1.6, phi_tr=0.04, phi_br=2.13) _check_unitary(bs) def test_PS_unitary(): bs = comp.PS(phi=0.82) _check_unitary(bs) def test_WP_unitary(): wp = comp.WP(delta=0.24, xsi=0.58) _check_unitary(wp) def test_PR_unitary(): wp = comp.PR(delta=0.37) _check_unitary(wp) ================================================ FILE: tests/components/test_controlled_gates.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import cmath as cm from perceval import BasicState, Port, Processor, Encoding, PostSelect from perceval.algorithm import Analyzer from perceval.utils import generate_all_logical_states from perceval.simulators import SimulatorFactory from perceval.components import catalog, BS, get_basic_state_from_ports def check_controlled_gates_and_get_performance(n, alpha, processor, herald_states, error=1E-6): """Check if the CC..Z(alpha) is correct Meaning checking that the CC...Z(alpha) gate probability amplitude matrix should be: Id_(2^n) +(exp(i.alpha)-1) |1...1><1...1| :param processor: CCZ processor to check, we assume that ctrl is on [0,1] and [2,3] and data on [4,5] :param herald_states: Basic state corresponding to the heralded or ancillary modes at the end of the circuit :param error: Tolerance for the modulus of the prob_amplitude (when not expecting 0), defaults to 1E-6 This param represent whether this CCZ gate is balanced """ ports = [Port(Encoding.DUAL_RAIL, "")] * n states = [get_basic_state_from_ports( ports, state) * herald_states for state in generate_all_logical_states(n)] sim = SimulatorFactory().build(processor) data_state = BasicState("|0,1"+(n-1)*",0,1"+">") * herald_states modulus_value = None phase_value = None for i_state in states: for o_state in states: pa = sim.prob_amplitude(i_state, o_state) modulus = abs(pa) phase = cm.phase(pa) if i_state == o_state: if modulus_value is None: modulus_value = modulus assert pytest.approx(modulus, error) == modulus_value assert modulus != 0 if i_state != data_state: if phase_value is None: phase_value = phase assert pytest.approx(phase) == phase_value % (2 * cm.pi) else: delta_phase = (phase - (phase_value + alpha) + cm.pi) % (2*cm.pi) - cm.pi assert pytest.approx(delta_phase) == 0 else: assert pytest.approx(modulus) == 0 return modulus_value def test_controlled_gates(): for n in [2, 3, 4]: for alpha in [cm.pi, cm.pi/3, -cm.pi/4, 1.]: # Testing phases and modulus of CCZ check_controlled_gates_and_get_performance(n, alpha, catalog['postprocessed controlled gate'].build_processor(n=n, alpha=alpha), BasicState("|0"+(2*n-1)*",0"+">")) def test_ccz_and_toffoli_phases_and_modulus(): # Testing phases and modulus of CCZ modulus_ccz = check_controlled_gates_and_get_performance(3, cm.pi, catalog['postprocessed ccz'].build_processor(), BasicState("|0,0,0,0,0,0>")) # Testing phases and modulus of Toffoli by transforming it in a CCZ gate with Hadamard gates ccz = Processor("SLOS", 6) ccz.add(4, BS.H()) \ .add(0, catalog["toffoli"].build_processor()) \ .add(4, BS.H()) modulus_ccz_with_toffoli = check_controlled_gates_and_get_performance(3, cm.pi, catalog['postprocessed ccz'].build_processor(), BasicState("|0,0,0,0,0,0>")) assert modulus_ccz == pytest.approx(modulus_ccz_with_toffoli) # Testing truth table of Toffoli (redundant with above test) toffoli = catalog['toffoli'].build_processor() state_dict = {get_basic_state_from_ports(toffoli.experiment._out_ports, state): str( state) for state in generate_all_logical_states(3)} a_toffoli = Analyzer(toffoli, input_states=state_dict) a_toffoli.compute(expected={"|000>": "|000>", "|001>": "|001>", "|010>": "|010>", "|011>": "|011>", "|100>": "|100>", "|101>": "|101>", "|110>": "|111>", "|111>": "|110>"}) assert a_toffoli.fidelity == 1 # Checking that Toffoli performance is the modulus**2 of the CCZ gate (since Toffoli comes from CCZ gate) assert a_toffoli.performance == pytest.approx(modulus_ccz**2) @pytest.mark.skip(reason="redundant with overhead test") def test_inverted_sub_cnot(): """Heralding ctrl one after the other to do the same test as test_inverted_cnot in tests/test_2qbits_gates.py """ toffoli = catalog["toffoli"].build_processor() toffoli.remove_port(2) \ .add_herald(2, 0)\ .add_herald(3, 1) toffoli.clear_postselection() toffoli.set_postselection(PostSelect("[0,1]==1 & [4,5]==1")) cnot0 = Processor("SLOS", 4) cnot0.add([0, 1], BS.H()) \ .add([2, 3], BS.H()) \ .add([2, 3, 0, 1], toffoli) \ .add([0, 1], BS.H()) \ .add([2, 3], BS.H()) state_dict = {get_basic_state_from_ports(cnot0._out_ports, state): str( state) for state in generate_all_logical_states(2)} a_cnot0 = Analyzer(cnot0, input_states=state_dict) a_cnot0.compute(expected={"00": "00", "01": "01", "10": "11", "11": "10"}) assert a_cnot0.fidelity == 1 toffoli = catalog["toffoli"].build_processor() toffoli.remove_port(0) \ .add_herald(0, 0)\ .add_herald(1, 1) toffoli.clear_postselection() toffoli.set_postselection(PostSelect("[2,3]==1 & [4,5]==1")) cnot1 = Processor("SLOS", 4) cnot1.add([0, 1], BS.H()) \ .add([2, 3], BS.H()) \ .add([2, 3, 0, 1], toffoli) \ .add([0, 1], BS.H()) \ .add([2, 3], BS.H()) a_cnot1 = Analyzer(cnot1, input_states=state_dict) a_cnot1.compute(expected={"00": "00", "01": "01", "10": "11", "11": "10"}) assert a_cnot1.fidelity == 1 ================================================ FILE: tests/components/test_decomposition.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from pathlib import Path import pytest import perceval as pcvl import perceval.components as comp from perceval.utils.algorithms.circuit_optimizer import CircuitOptimizer from perceval.utils.algorithms import norm import numpy as np TEST_DATA_DIR = Path(__file__).resolve().parent.parent / 'data' def test_perm_0(): c = pcvl.Circuit(4).add(0, comp.PERM([3, 1, 2, 0])) c1 = pcvl.Circuit.decomposition(pcvl.Matrix(c.U), pcvl.catalog["mzi phase last"].build_circuit(), phase_shifter_fn=comp.PS, shape=pcvl.InterferometerShape.TRIANGLE, permutation=comp.PERM) assert c1.describe().replace("\n", "").replace(" ", "") == """ Circuit(4).add((0, 1), PERM([1, 0])) .add((1, 2), PERM([1, 0])) .add((2, 3), PERM([1, 0])) .add((1, 2), PERM([1, 0])) .add((0, 1), PERM([1, 0]))""".replace("\n", "").replace(" ", "") def test_basic_perm_triangle(): c = pcvl.Circuit(2).add(0, comp.PERM([1, 0])) c1 = pcvl.Circuit.decomposition(pcvl.Matrix(c.U), pcvl.catalog["mzi phase last"].build_circuit(), shape=pcvl.InterferometerShape.TRIANGLE) m1 = c1.compute_unitary(use_symbolic=False) assert pytest.approx(0, abs=1e-7) == abs(m1[0][0]) assert pytest.approx(1, abs=1e-7) == abs(m1[0][1]) assert pytest.approx(1, abs=1e-7) == abs(m1[1][0]) assert pytest.approx(0, abs=1e-7) == abs(m1[1][1]) def test_basic_perm_triangle_bs(): c = pcvl.Circuit(2).add(0, comp.PERM([1, 0])) ub = comp.BS(theta=pcvl.Parameter("theta")) c1 = pcvl.Circuit.decomposition(pcvl.Matrix(c.U), ub, shape=pcvl.InterferometerShape.TRIANGLE) m1 = c1.compute_unitary(use_symbolic=False) assert pytest.approx(0, abs=1e-7) == abs(m1[0][0]) assert pytest.approx(1, abs=1e-7) == abs(m1[0][1]) assert pytest.approx(1, abs=1e-7) == abs(m1[1][0]) assert pytest.approx(0, abs=1e-7) == abs(m1[1][1]) def test_basic_perm_rectangle(): c = pcvl.Circuit(2).add(0, comp.PERM([1, 0])) co = CircuitOptimizer() c1 = co.optimize_rectangle(pcvl.Matrix(c.U)) m1 = c1.compute_unitary() assert pytest.approx(1, rel=1e-3) == abs(m1[0][0])+1 assert pytest.approx(1, rel=1e-3) == abs(m1[0][1]) assert pytest.approx(1, rel=1e-3) == abs(m1[1][0]) assert pytest.approx(1, rel=1e-3) == abs(m1[1][1])+1 def test_perm_triangle(): c = pcvl.Circuit(4).add(0, comp.PERM([3, 1, 2, 0])) m = c.compute_unitary() c1 = pcvl.Circuit.decomposition(pcvl.Matrix(c.U), pcvl.catalog["mzi phase last"].build_circuit(), shape=pcvl.InterferometerShape.TRIANGLE) m1 = c1.compute_unitary() np.testing.assert_array_almost_equal(abs(m), abs(m1), decimal=6) @pytest.mark.skip(reason="Optimization does not converge with unusual template component") def test_perm_rectangle_bs_0(): c = pcvl.Circuit(3).add(0, comp.PERM([1, 0, 2])) def gen_template_component(i: int): return (pcvl.Circuit(2) // comp.PS(phi=pcvl.Parameter(f"phi_a{i}")) // comp.BS(theta=pcvl.Parameter(f"theta{i}"))) co = CircuitOptimizer() co.threshold = 0.1 c1 = co.optimize_rectangle(pcvl.Matrix(c.U), gen_template_component, phase_at_output=True) assert norm.fidelity(c.compute_unitary(), c1.compute_unitary()) > 1 - co.threshold @pytest.mark.skip(reason="Optimization does not converge with unusual template component") def test_perm_rectangle_bs_1(): c = pcvl.Circuit(3).add(0, comp.PERM([2, 1, 0])) ub = (pcvl.Circuit(2) // (0, comp.PS(phi=pcvl.Parameter("φ_a"))) // comp.BS(theta=pcvl.P("theta"))) m = c.compute_unitary() c1 = pcvl.Circuit.decomposition(pcvl.Matrix(c.U), ub, shape=pcvl.InterferometerShape.RECTANGLE, constraints=[(0, None)]) m1 = c1.compute_unitary() np.testing.assert_array_almost_equal(abs(m), abs(m1), decimal=6) c2 = pcvl.Circuit.decomposition(pcvl.Matrix(c.U), ub, shape=pcvl.InterferometerShape.RECTANGLE, constraints=[(None, 0)]) assert c2 is None def test_id_decomposition_rectangle(): # identity matrix decompose as ... identity c = pcvl.Circuit(4) co = CircuitOptimizer() c1 = co.optimize_rectangle(pcvl.Matrix(c.U), pcvl.catalog["mzi phase first"].generate, phase_at_output=True) m1 = c1.compute_unitary() assert norm.fidelity(c.compute_unitary(), m1) > 1 - co.threshold def test_id_decomposition_triangle(): # identity matrix decompose as ... identity c = pcvl.Circuit(4) c1 = pcvl.Circuit.decomposition(pcvl.Matrix(c.U), pcvl.catalog["mzi phase last"].build_circuit(), shape=pcvl.InterferometerShape.TRIANGLE) np.testing.assert_array_almost_equal(pcvl.Matrix.eye(4, use_symbolic=False), c1.compute_unitary(), decimal=6) assert c1.ncomponents() == 0 # With ignore_identity_block=False, the decomposed circuit contains multiple MZIs with trivial values c2 = pcvl.Circuit.decomposition(pcvl.Matrix(c.U), pcvl.catalog["mzi phase last"].build_circuit(), shape=pcvl.InterferometerShape.TRIANGLE, ignore_identity_block=False) assert c2 is not None m2 = c2.compute_unitary() assert pytest.approx(1, abs=1e-7) == abs(m2[0][0]) assert pytest.approx(0, abs=1e-7) == abs(m2[0][1]) assert pytest.approx(0, abs=1e-7) == abs(m2[1][0]) assert pytest.approx(1, abs=1e-7) == abs(m2[1][1]) assert c2.ncomponents() == 6*pcvl.catalog["mzi phase last"].build_circuit().ncomponents() def test_any_unitary_triangle(): with open(TEST_DATA_DIR / 'u_random_3', "r") as f: m = pcvl.Matrix(f) c1 = pcvl.Circuit.decomposition(m, pcvl.catalog["mzi phase last"].build_circuit(), phase_shifter_fn=comp.PS, shape=pcvl.InterferometerShape.TRIANGLE, max_try=10) assert c1 is not None np.testing.assert_array_almost_equal(m, c1.compute_unitary(), decimal=6) def test_any_unitary_rectangle(): with open(TEST_DATA_DIR / 'u_random_8', "r") as f: m = pcvl.Matrix(f) co = CircuitOptimizer() c1 = co.optimize_rectangle(m) m1 = c1.compute_unitary() assert norm.fidelity(m, m1) > 1 - co.threshold # You can decompose with another form of MZI as long as your template remains universal # In this case, that means putting a layer of PS at the input of the circuit c2 = co.optimize_rectangle(m, pcvl.catalog["mzi phase last"].generate, phase_at_output=False) m2 = c2.compute_unitary() assert norm.fidelity(m, m2) > 1 - co.threshold def test_simple_phase(): for m in [pcvl.Matrix([[0, 1j], [1, 0]]), pcvl.Matrix([[0, 1j], [-1, 0]]), pcvl.Matrix([[1j, 0], [0, -1]])]: c1 = pcvl.Circuit.decomposition(m, pcvl.catalog["mzi phase last"].build_circuit(), phase_shifter_fn=comp.PS, shape=pcvl.InterferometerShape.TRIANGLE, max_try=5) assert c1 is not None np.testing.assert_array_almost_equal(m, c1.compute_unitary(), decimal=6) def test_decompose_non_unitary(): m = np.array([[(i and j) and (i+j*1j)/np.sqrt(i*i+j*j) or 0 for i in range(5)] for j in range(5)]) with pytest.raises(ValueError): pcvl.Circuit.decomposition(m, pcvl.catalog["mzi phase last"].build_circuit(), shape=pcvl.InterferometerShape.TRIANGLE, max_try=5) @pytest.mark.long_test def test_decomposition_large(): with open(TEST_DATA_DIR / 'u_random_8', "r") as f: m = pcvl.Matrix(f) c1 = pcvl.Circuit.decomposition(m, pcvl.catalog["mzi phase last"].build_circuit(), phase_shifter_fn=comp.PS, shape=pcvl.InterferometerShape.TRIANGLE, max_try=1) assert c1 is not None np.testing.assert_array_almost_equal(m, c1.compute_unitary(), decimal=6) def test_decomposition_perm(): c1 = pcvl.Circuit.decomposition(pcvl.Matrix(comp.PERM([3, 1, 0, 2]).U), comp.BS(theta=pcvl.P("theta")), phase_shifter_fn=comp.PS) assert c1 is not None def test_decomposition_inverse_rx(): ub = (pcvl.Circuit(2) // (0, comp.BS.Rx(theta=pcvl.Parameter("theta"))) // (1, comp.PS(phi=pcvl.Parameter("phi")))) u = pcvl.Matrix.random_unitary(6) c = pcvl.Circuit.decomposition(u, ub, inverse_v=True, inverse_h=True, phase_shifter_fn=comp.PS) np.testing.assert_array_almost_equal(u, c.compute_unitary(), decimal=6) def test_decomposition_inverse_h(): ub = (pcvl.Circuit(2) // (0, comp.BS.H(theta=pcvl.Parameter("theta"))) // (1, comp.PS(phi=pcvl.Parameter("phi")))) u = pcvl.Matrix.random_unitary(6) c = pcvl.Circuit.decomposition(u, ub, inverse_v=True, inverse_h=True, phase_shifter_fn=comp.PS) np.testing.assert_array_almost_equal(u, c.compute_unitary(), decimal=6) ================================================ FILE: tests/components/test_detectors.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from unittest.mock import patch import pytest from perceval.components import BS, PERM from perceval.components.detector import get_detection_type, Detector, DetectionType, BSLayeredPPNR, \ check_heralds_detectors from perceval.utils import BasicState from .._test_utils import LogChecker import perceval as pcvl def test_detector(): pnr_detector = Detector.pnr() assert pnr_detector.type == DetectionType.PNR for i in range(10): assert pnr_detector.detect(i) == BasicState([i]) th_detector = Detector.threshold() assert th_detector.type == DetectionType.Threshold for i in range(10): assert th_detector.detect(i) == BasicState([i if i < 2 else 1]) max_detection = 4 limited_pnr_detector = Detector(max_detections=max_detection) assert limited_pnr_detector.type == DetectionType.PPNR for i in range(10): assert limited_pnr_detector.detect(i) == BasicState([i if i <= max_detection else max_detection]) def test_interleaved_ppnr(): s = [BasicState([i]) for i in range(7)] detector_2wires = Detector.ppnr(n_wires=2) # The detector contains 2 independent detecting-able systems res = detector_2wires.detect(2) # 2 photons simultaneously hit the PPNR detector assert s[0] not in res # At least one photon gets detected => |0> does not appear assert s[1] in res and res[s[1]] == pytest.approx(0.5) assert s[2] in res and res[s[2]] == pytest.approx(0.5) assert len(res) == 2 # Can't detect 3 simultaneous photons with only 2 wires res = detector_2wires.detect(3) # Now with 3 incoming photons assert s[0] not in res assert s[1] in res and res[s[1]] == pytest.approx(0.25) assert s[2] in res and res[s[2]] == pytest.approx(0.75) assert len(res) == 2 detector_3wires = Detector.ppnr(n_wires=3, wire_efficiency=0.7) # With 3 wires and a limited wire efficiency of 0.7 res = detector_3wires.detect(3) assert s[0] in res and res[s[0]] == pytest.approx(0.027) # Probability to detect 0 photons is no longer 0 assert s[1] in res and res[s[1]] == pytest.approx(3367/9000) assert s[2] in res and res[s[2]] == pytest.approx(196/375) assert s[3] in res and res[s[3]] == pytest.approx(343/4500) assert len(res) == 4 # Can't detect more than 3 simultaneous photons with 3 wires assert 3 in detector_3wires._cache # Check that the result is cached assert detector_3wires._cache[3] == res detector_5wires = Detector.ppnr(n_wires=5, wire_efficiency=0.7) # Adding wires increases the probability of catching all photons res = detector_5wires.detect(3) assert s[0] in res and res[s[0]] == pytest.approx(0.027) assert s[1] in res and res[s[1]] == pytest.approx(36365/125000) assert s[2] in res and res[s[2]] == pytest.approx(1617/3125) assert s[3] in res and res[s[3]] == pytest.approx(1029/6250) assert len(res) == 4 detector_5wires_higher_efficiency = Detector.ppnr(n_wires=5, wire_efficiency=0.9) # Higher efficiency increases the probability of catching all photons res = detector_5wires_higher_efficiency.detect(3) assert s[0] in res and res[s[0]] == pytest.approx(0.001) assert s[1] in res and res[s[1]] == pytest.approx(2619/25000) assert s[2] in res and res[s[2]] == pytest.approx(1701/3125) assert s[3] in res and res[s[3]] == pytest.approx(2187/6250) assert len(res) == 4 detector_5wires_higher_efficiency_2max = Detector.ppnr(n_wires=5, max_detections=2, wire_efficiency=0.9) # Now limit the detected photon count at 2 res = detector_5wires_higher_efficiency_2max.detect(3) # Here we get the same results as above, but the probability for 2 and 3 detections are summed assert s[0] in res and res[s[0]] == pytest.approx(0.001) assert s[1] in res and res[s[1]] == pytest.approx(2619/25000) assert s[2] in res and res[s[2]] == pytest.approx(5589/6250) # = p2_5wires + p3_5wires assert len(res) == 3 detector_infinity_wires = Detector.ppnr(wire_efficiency=0.7) res = detector_infinity_wires.detect(3) assert s[0] in res and res[s[0]] == pytest.approx(0.027) # Probability to detect 0 photons is no longer 0 assert s[1] in res and res[s[1]] == pytest.approx(3*0.7*0.09) assert s[2] in res and res[s[2]] == pytest.approx(3*0.49*0.3) assert s[3] in res and res[s[3]] == pytest.approx(0.7**3) assert len(res) == 4 def test_bs_layered_ppnr(): detector = BSLayeredPPNR(1) assert detector.type == DetectionType.PPNR s0 = BasicState([0]) s1 = BasicState([1]) s2 = BasicState([2]) result = detector.detect(0) assert result == s0 result = detector.detect(1) assert result == s1 result = detector.detect(2) assert s0 not in result assert result[s1] == pytest.approx(0.5) assert result[s2] == pytest.approx(0.5) result = detector.detect(3) assert s0 not in result assert result[s1] == pytest.approx(0.25) assert result[s2] == pytest.approx(0.75) def test_bs_layered_ppnr_circuit(): refl = 0.55 detector = BSLayeredPPNR(1, refl) ppnr_circuit = detector.create_circuit() assert ppnr_circuit.ncomponents() == 1 assert isinstance(ppnr_circuit[0, 0], BS) assert ppnr_circuit[0, 0].reflectivity == pytest.approx(refl) detector = BSLayeredPPNR(3, refl) ppnr_circuit = detector.create_circuit() assert ppnr_circuit.ncomponents() == 9 # 1 + 2 + 4 = 7 beam splitters + 2 permutations expected = ((0, BS), (0, PERM), (0, BS), (2, BS), (0, PERM), (0, BS), (2, BS), (4, BS), (6, BS)) for component, expectation in zip(ppnr_circuit._components, expected): assert component[0][0] == expectation[0] assert isinstance(component[1], expectation[1]) def test_bs_layered_ppnr_bad_usage(): with pytest.raises(AssertionError): BSLayeredPPNR(0) with pytest.raises(AssertionError): BSLayeredPPNR(1, -0.2) with pytest.raises(AssertionError): BSLayeredPPNR(1, 1.1) def test_detection_type(): pnr_detector_list = [Detector.pnr()] * 3 # Only PNR detectors thr_detector_list = [Detector.threshold()] * 3 # Only threshold detectors mixed_detector_list = [BSLayeredPPNR(1), Detector.pnr(), Detector.threshold()] assert get_detection_type(pnr_detector_list) == DetectionType.PNR assert get_detection_type(thr_detector_list) == DetectionType.Threshold # PPNR means mixed detectors in this context assert get_detection_type(pnr_detector_list + thr_detector_list) == DetectionType.Mixed assert get_detection_type(mixed_detector_list) == DetectionType.Mixed @patch.object(pcvl.utils.logging.ExqaliburLogger, "warn") def test_incompatible_heralds(mock_warn): detector_th = Detector.threshold() detector_2 = Detector.ppnr(24, 2) detector_pnr = Detector.pnr() # Single detectors test assert check_heralds_detectors({0: 213}, [None]) assert check_heralds_detectors({0: 213}, [detector_pnr]) assert check_heralds_detectors({0: 0}, [detector_th]) assert check_heralds_detectors({0: 1}, [detector_th]) with LogChecker(mock_warn): assert not check_heralds_detectors({0: 2}, [detector_th]) assert check_heralds_detectors({0: 0}, [detector_2]) assert check_heralds_detectors({0: 1}, [detector_2]) assert check_heralds_detectors({0: 2}, [detector_2]) with LogChecker(mock_warn): assert not check_heralds_detectors({0: 3}, [detector_2]) # Mixed detectors test assert check_heralds_detectors({0: 14, 1: 1, 2: 2}, [None, detector_th, detector_2]) assert check_heralds_detectors({0: 14, 1: 1, 2: 2}, [detector_pnr, detector_th, detector_2]) with LogChecker(mock_warn): assert not check_heralds_detectors({0: 14, 1: 2, 2: 0}, [None, detector_th, detector_2]) with LogChecker(mock_warn): assert not check_heralds_detectors({0: 14, 1: 1, 2: 3}, [None, detector_th, detector_2]) ================================================ FILE: tests/components/test_ff_configurator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import numpy as np from perceval.components import Circuit, BS, PS from perceval.utils import BasicState, P from perceval.components.feed_forward_configurator import FFCircuitProvider, FFConfigurator def test_generic(): default_circuit = BS() m = 3 offset = 1 config = FFCircuitProvider(m, offset, default_circuit) assert config.default_circuit == default_circuit, "Incorrect default circuit" place = (2, 3, 4) assert config._max_circuit_size == 2, "Incorrect maximum circuit size" assert config.config_modes(place) == (6, 7), "Incorrect place of configured circuit" assert config.configure(BasicState(m * [0])) == default_circuit, "Incorrect output circuit" config.circuit_offset = -1 assert config.config_modes(place) == (0, 1), "Incorrect place of configured circuit" def test_ff_circuit_provider(): default_circuit = Circuit(2) m = 3 offset = 1 tested_circuit = BS() config = FFCircuitProvider(m, offset, default_circuit) config.add_configuration([1, 1, 0], tested_circuit) assert config.configure(BasicState(m * [0])) == default_circuit, "Incorrect output default circuit" assert config.configure(BasicState([1, 1, 0])) == tested_circuit, "Incorrect output circuit" config.reset_map() assert config.configure(BasicState([1, 1, 0])) == default_circuit, "Incorrect output circuit" config.circuit_map = {BasicState([1, 1, 0]): tested_circuit} assert config.configure(BasicState([1, 1, 0])) == tested_circuit, "Incorrect output circuit" def test_ff_circuit_provider_failures(): default_circuit = BS(P("theta0")) m = 3 offset = 1 ffc = FFCircuitProvider(m, offset, default_circuit) with pytest.raises(AssertionError): ffc.add_configuration((0, 1), BS()) # Wrong number of modes with pytest.raises(RuntimeError): ffc.add_configuration((1, 0, 1), BS(P("theta0"))) # Repeated parameter def test_ffconfigurator(): controlled_circuit = BS.H() // PS(phi=P("phi_a")) // BS.H() ffc = FFConfigurator(2, 0, controlled_circuit, default_config={"phi_a": 1}) assert ffc.circuit_template() == controlled_circuit ffc.add_configuration((0, 1), {"phi_a": 0}) ffc.add_configuration((1, 0), {"phi_a": np.pi}) c_0_1 = ffc.configure(BasicState([0, 1])) assert float(c_0_1._components[1][1].param("phi")) == 0 assert np.allclose(c_0_1.compute_unitary(), np.eye(2)) c_1_0 = ffc.configure(BasicState([1, 0])) assert float(c_1_0._components[1][1].param("phi")) == np.pi c_2_0 = ffc.configure(BasicState([2, 0])) # Unknown detection triggers default configuration assert float(c_2_0._components[1][1].param("phi")) == 1 def test_ffconfigurator_failures(): controlled_circuit = BS.H(theta=P("theta0")) // PS(phi=P("phi_a")) // BS.H() with pytest.raises(NameError): FFConfigurator(2, 0, controlled_circuit, default_config={"toto0": 0.5, "phi_c": 1}) with pytest.raises(NameError): FFConfigurator(2, 0, controlled_circuit, default_config={"phi_a": 1, "phi_c": 1}) with pytest.raises(ValueError): # Not enough params in config FFConfigurator(2, 0, controlled_circuit, default_config={"phi_a": 1}) ffc = FFConfigurator(2, 0, controlled_circuit, default_config={"theta0": 2, "phi_a": 1}) with pytest.raises(NameError): ffc.add_configuration((0, 1), {"toto0": 0.5, "phi_c": 1}) with pytest.raises(NameError): ffc.add_configuration((0, 1), {"phi_a": 1, "phi_c": 1}) with pytest.raises(ValueError): # Not enough params in config ffc.add_configuration((0, 1), {"phi_a": 1}) ================================================ FILE: tests/components/test_generic_interferometer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest from perceval.components import GenericInterferometer, catalog, BS, PS from perceval.backends import SLOSBackend from perceval.utils import BasicState, P size_identity = 6 mzi_generator_func = catalog['mzi phase last'].generate def test_variable_parameters_count(): def _gen_bs(i: int): return BS(theta=P(f"theta{i}")) c = GenericInterferometer(5, _gen_bs) assert len(c.get_parameters()) == 5*4/2 c = GenericInterferometer(5, _gen_bs, depth=1) assert len(c.get_parameters()) == 2 c = GenericInterferometer(5, _gen_bs, depth=2) assert len(c.get_parameters()) == 4 @pytest.mark.parametrize('interferometer', [ GenericInterferometer(size_identity, mzi_generator_func), GenericInterferometer(size_identity, mzi_generator_func, phase_shifter_fun_gen=lambda i: PS(P(f"phi{i}")), phase_at_output=True), GenericInterferometer(size_identity, mzi_generator_func, phase_shifter_fun_gen=lambda i: PS(P(f"phi{i}")), phase_at_output=False) ]) def test_set_identity(interferometer): input_state = BasicState([1]*size_identity) # One photon per mode as input interferometer.set_identity_mode() slos = SLOSBackend() slos.set_circuit(interferometer) slos.set_input_state(input_state) assert slos.probability(output_state=input_state) == pytest.approx(1) # Detect one photon per mode def test_set_param_list(): size = 12 values = [.0, .1, .2, .3, .4, .5, .6, .7, .8, .9] interferometer = GenericInterferometer(size, mzi_generator_func) interferometer.set_param_list(values, (0, 0), m=2) # With m=2, only one row of phase shifters get impacted (on mode 1, given the mzi we used) # The 10 first phase shifter of this row get phi=values[idx] indexes = [1, 3, 7, 9, 13, 15, 19, 21, 25, 27] for idx, phase_shifter_pos in enumerate(indexes): assert float(interferometer[1, phase_shifter_pos].get_parameters()[0]) == pytest.approx(values[idx]) # next phase shifters still have a variable phi: assert interferometer[1, 31].get_parameters()[0].defined == False params_with_numerical_value = [p for p in interferometer.get_parameters() if p.defined] assert len(params_with_numerical_value) == len(values) # Moving 2 MZI down, means 4 modes down interferometer = GenericInterferometer(size, mzi_generator_func) interferometer.set_param_list(values, (0, 2), m=2) for idx, phase_shifter_pos in enumerate(indexes): assert float(interferometer[5, phase_shifter_pos].get_parameters()[0]) == pytest.approx(values[idx]) # Moving 1 MZI right, means 6 components right interferometer = GenericInterferometer(size, mzi_generator_func) interferometer.set_param_list(values, (1, 0), m=2) for idx, phase_shifter_pos in enumerate(indexes): assert float(interferometer[1, phase_shifter_pos+6].get_parameters()[0]) == pytest.approx(values[idx]) # Starting too right, can get out of the interferometer interferometer = GenericInterferometer(size, mzi_generator_func) with pytest.raises(ValueError): interferometer.set_param_list(values, (3, 0), m=2) # Reshaping by giving a higher m value, will impact more modes on insertion interferometer = GenericInterferometer(size, mzi_generator_func) interferometer.set_param_list(values, (0, 0), m=4) for idx, (x, y) in enumerate([(1, 1), (1, 3), (3, 1), (3, 3), (2, 3), (2, 5), (1, 7), (1, 9), (3, 7), (3, 9)]): assert float(interferometer[x, y].get_parameters()[0]) == pytest.approx(values[idx]) def test_set_params_from_other(): small_interferometer = GenericInterferometer(4, mzi_generator_func) small_interferometer.set_identity_mode() # Give a value to phases big_interferometer = GenericInterferometer(8, mzi_generator_func, phase_shifter_fun_gen=lambda i: PS(P(f"phi_L{i}"))) big_interferometer.set_params_from_other(small_interferometer, (0, 0)) big_interferometer.remove_phase_layer() for (x, y) in [(1, 1), (1, 3), (3, 1), (3, 3), (2, 3), (2, 5), (1, 7), (1, 9), (3, 7), (3, 9)]: assert float(big_interferometer[x, y].get_parameters()[0]) == pytest.approx(math.pi) params_with_numerical_value = [p for p in big_interferometer.get_parameters() if p.defined] assert len(params_with_numerical_value) == len(small_interferometer.get_parameters()) with pytest.raises(ValueError): big_interferometer.set_params_from_other(small_interferometer, (6, 2)) ================================================ FILE: tests/components/test_loss_channel.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval import Processor, Unitary, LC, Matrix, BSDistribution, BasicState, SVDistribution, NoiseModel from perceval.algorithm import Sampler from .._test_utils import assert_bsd_close U = Matrix.random_unitary(2) loss = .3 cd = (Processor("SLOS", 2) .add(0, Unitary(U)) .add(0, LC(loss)) .add(1, LC(loss))) input_state = BasicState([1, 1]) cd.with_input(input_state) cd.min_detected_photons_filter(0) def test_lc_minimal(): p = Processor("SLOS", 1).add(0, LC(loss)) p.with_input(SVDistribution(BasicState([2]))) p.min_detected_photons_filter(0) expected_svd = BSDistribution() expected_svd[BasicState([0])] = loss ** 2 expected_svd[BasicState([1])] = 2 * loss * (1 - loss) expected_svd[BasicState([2])] = (1 - loss) ** 2 res = p.probs()["results"] assert_bsd_close(res, expected_svd) def test_lc_commutative(): # All LC on the input or on the output of the processor yield the same results cg = (Processor("SLOS", 2) .add(0, LC(loss)) .add(1, LC(loss)) .add(0, Unitary(U))) cg.with_input(input_state) cg.min_detected_photons_filter(0) assert_bsd_close(cg.probs()["results"], cd.probs()["results"]) def test_lc_source_losses_equivalence(): # When the losses are balanced p = Processor("SLOS", Unitary(U), NoiseModel(transmittance=1 - loss)) p.with_input(input_state) p.min_detected_photons_filter(0) sampler = Sampler(p) real_out = sampler.probs()["results"] assert_bsd_close(real_out, cd.probs()["results"]) def test_lc_empty_modes(): p = Processor("SLOS", 2).add(0, LC(loss)) p.min_detected_photons_filter(0) p.with_input(input_state) sampler = Sampler(p) real_out = sampler.probs()["results"] assert_bsd_close(real_out, BSDistribution( {BasicState([0, 1]): loss, BasicState([1, 1]): 1 - loss} )) ================================================ FILE: tests/components/test_match.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest import perceval as pcvl from perceval.components.unitary_components import BS, PS, PERM from perceval.utils.algorithms.optimize import optimize from perceval.utils.algorithms import norm from perceval.utils import random_seed def setup_function(_): # The random seed is fixed for this test suite, because the optimization problem randomly fails to be accurate # enough random_seed(0) def teardown_function(_): # Revert to an arbitrary random seed after these tests to not interfere with any subsequent call random_seed() def _bs_rx(r): return BS(BS.r_to_theta(r)) def _bs_h(r): return BS.H(BS.r_to_theta(r)) def test_match_elementary(): bs = _bs_rx(0.42) matched = bs.match(BS(theta=pcvl.P("theta"))) assert matched is not None assert pytest.approx(1.7314869) == matched.v_map.get("theta", None) assert matched.pos_map == {0: 0} def test_match_nomatch(): bs = PS(phi=0.3) assert bs.match(BS.H(theta=pcvl.P("theta"))) is None def test_match_perm(): bs = PERM([1, 0]) pattern = BS.H(theta=pcvl.P("theta"), phi_tl=pcvl.P("phi_tl"), phi_bl=pcvl.P("phi_bl"), phi_tr=pcvl.P("phi_tr")) matched = bs.match(pattern) assert matched is not None theta = matched.v_map.get("theta", None) assert pytest.approx(math.pi) == float(theta) or pytest.approx(3*math.pi) == float(theta) def test_match_double(): bs = BS.H() // PS(0.5) pattern = BS.H() // PS(pcvl.P("phi")) matched = bs.match(pattern) assert matched is not None assert pytest.approx(1/2) == matched.v_map.get("phi", None) assert matched.pos_map == {0: 0, 1: 1} bs = BS.H() // (1, PS(0.5)) pattern = BS.H() // PS(pcvl.P("phi")) matched = bs.match(pattern) assert not matched def test_match_rec(): mzi = BS.H() // PS(0.5) // BS.H() // (1, PS(0.3)) pattern = BS.H() // (1, PS(pcvl.P("phi"))) matched = mzi.match(pattern) assert matched is None pattern = BS.H() // (1, PS(pcvl.P("phi"))) matched = mzi.match(pattern, browse=True) assert matched is not None assert matched.pos_map == {2: 0, 3: 1} def test_match_rec_inv(): c = pcvl.Circuit(3) // (1, BS.H()) // (0, BS.H()) // (1, BS.H()) pattern = pcvl.Circuit(3) // (0, BS.H()) // (1, BS.H()) // (0, BS.H()) matched = c.match(pattern) assert matched is None def test_match_simple_seq(): p2 = BS.H() // BS.H() c = BS.H() // BS.H() matched = c.match(p2) assert matched assert matched.pos_map == {0: 0, 1: 1} def test_subnodes_0(): bs = pcvl.Circuit(2).add(0, BS.H()) assert bs.find_subnodes(0) == [None, None] bs = pcvl.Circuit(3).add(0, BS.H()).add(1, PS(0.2)).add(1, BS.H()) assert bs.find_subnodes(0) == [None, (1, 0)] assert bs.find_subnodes(1) == [(2, 0)] # def test_replace_R_by_theta_1(): # p0a = BS(theta=pcvl.P("theta")) # p0b = BS(theta=pcvl.P("theta"), phi_tl=math.pi) # random_theta = (random.random()-0.5) * math.pi # a = BS(theta=random_theta) # matched = a.match(p0a) # if matched is None: # matched = a.match(p0b) # assert matched # assert pytest.approx(math.cos(random_theta)**2) == matched.v_map.get("R", None) def test_match_rewrite_phase(): a = PS(0.4) // PS(1.4) pattern2 = pcvl.Circuit(1, name="pattern") // PS(pcvl.P("phi1")) // PS(pcvl.P("phi2")) rewrite2 = pcvl.Circuit(1, name="rewrite") // PS(pcvl.P("phi")) matched = a.match(pattern2) for k, v in matched.v_map.items(): pattern2.param(k).set_value(v) v = pattern2.compute_unitary() res = optimize(rewrite2, v, norm.frobenius, sign=-1) assert pytest.approx(1) == res.fun+1 assert pytest.approx(v[0, 0]) == rewrite2.compute_unitary()[0, 0] def test_match_switch_phases(): a = pcvl.Circuit(2) // PS(0.4) // _bs_h(0.45) pattern3 = (pcvl.Circuit(2, name="pattern3") // (0, PS(pcvl.P("phi1"))) // (1, PS(pcvl.P("phip"))) // (0, _bs_h(0.45))) matched = a.match(pattern3, browse=True) assert matched is None a = pcvl.Circuit(2) // (0, PS(0.4)) // (1, PS(0.3)) // _bs_h(0.45) matched = a.match(pattern3, browse=True) assert matched is not None assert pytest.approx(0.4) == matched.v_map["phi1"] assert pytest.approx(0.3) == matched.v_map["phip"] pattern3_check = (pcvl.Circuit(2, name="pattern3") // (1, PS(pcvl.P("phip"))) // (0, _bs_h(0.45))) a = pcvl.Circuit(2) // (1, PS(0.3)) // (0, PS(0.4)) // _bs_h(0.45) matched = a.match(pattern3_check) assert matched is not None and matched.pos_map == {0: 0, 2: 1} matched = a.match(pattern3, browse=True) assert matched is not None assert pytest.approx(0.4) == matched.v_map["phi1"] assert pytest.approx(0.3) == matched.v_map["phip"] ================================================ FILE: tests/components/test_mode_connector.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval.components._mode_connector import ModeConnector, UnavailableModeException, \ InvalidMappingException from perceval.components import Processor, Circuit, Port, PortLocation from perceval.utils import Encoding slos = "SLOS" def test_connection_resolver_init(): in_modes = 6 p1 = Processor(slos, 8) p2 = Processor(slos, in_modes) connector = ModeConnector(p1, p2, {}) assert not connector._r_is_component assert connector._n_modes_to_connect == in_modes circuit = Circuit(in_modes) connector = ModeConnector(p1, circuit, {}) assert connector._r_is_component assert connector._n_modes_to_connect == in_modes def test_connection_int(): p1 = Processor(slos, 6) p2 = Processor(slos, 4) connector = ModeConnector(p1, p2, 0) assert connector.resolve() == {0: 0, 1: 1, 2: 2, 3: 3} connector = ModeConnector(p1, p2, 1) assert connector.resolve() == {1: 0, 2: 1, 3: 2, 4: 3} connector = ModeConnector(p1, p2, 2) assert connector.resolve() == {2: 0, 3: 1, 4: 2, 5: 3} with pytest.raises(UnavailableModeException): connector = ModeConnector(p1, p2, -1) connector.resolve() def test_connection_list_int(): p1 = Processor(slos, 8) p2 = Processor(slos, 6) mode_index_list = [1, 2, 3, 4, 5, 6] connector = ModeConnector(p1, p2, mode_index_list) assert connector.resolve() == {1: 0, 2: 1, 3: 2, 4: 3, 5: 4, 6: 5} mode_index_list = [6, 1, 5, 2, 3, 0] connector = ModeConnector(p1, p2, mode_index_list) assert connector.resolve() == {6: 0, 1: 1, 5: 2, 2: 3, 3: 4, 0: 5} mode_index_list = [6, 1, 5, 2, 3, 0, 4] # Too long connector = ModeConnector(p1, p2, mode_index_list) with pytest.raises(InvalidMappingException): connector.resolve() mode_index_list = [6, 1, 5, 2] # Too short connector = ModeConnector(p1, p2, mode_index_list) with pytest.raises(InvalidMappingException): connector.resolve() mode_index_list = [6, 6, 5, 2, 3, 0] # With duplicates connector = ModeConnector(p1, p2, mode_index_list) with pytest.raises(InvalidMappingException): connector.resolve() def test_connection_dict_int(): p1 = Processor(slos, 8) p2 = Processor(slos, 6) valid_mapping = {0: 2, 1: 4, 2: 5, 3: 0, 4: 1, 5: 3} connector = ModeConnector(p1, p2, valid_mapping) assert connector.resolve() == valid_mapping invalid_mapping = {-1: 2, 1: 4, 2: 5, 3: 0, 4: 1, 5: 3} # -1 is invalid connector = ModeConnector(p1, p2, invalid_mapping) with pytest.raises(UnavailableModeException): connector.resolve() invalid_mapping = {0: 2, 1: 4, 2: 5, 3: 0, 4: 1, 5: 3, 6: 6} # mapping too big connector = ModeConnector(p1, p2, invalid_mapping) with pytest.raises(InvalidMappingException): connector.resolve() def test_connection_dict_str(): """Test with port names""" p1 = Processor(slos, 4) p1.add_port(0, Port(Encoding.DUAL_RAIL, "q0"), PortLocation.OUTPUT) p1.add_port(2, Port(Encoding.DUAL_RAIL, "q1"), PortLocation.OUTPUT) p2 = Processor(slos, 4) p2.add_port(0, Port(Encoding.DUAL_RAIL, "in_A"), PortLocation.INPUT) p2.add_port(2, Port(Encoding.DUAL_RAIL, "in_B"), PortLocation.INPUT) connector = ModeConnector(p1, p2, {'q0': 'in_A', 'q1': 'in_B'}) assert connector.resolve() == {0: 0, 1: 1, 2: 2, 3: 3} connector = ModeConnector(p1, p2, {'q0': 'in_B', 'q1': 'in_A'}) assert connector.resolve() == {0: 2, 1: 3, 2: 0, 3: 1} connector = ModeConnector(p1, p2, {'q0': [2, 3], 'q1': [0, 1]}) assert connector.resolve() == {0: 2, 1: 3, 2: 0, 3: 1} connector = ModeConnector(p1, p2, {'q0': 1, 'q1': 'in_B'}) with pytest.raises(InvalidMappingException): connector.resolve() # imbalanced size (size of q0 is 2) connector = ModeConnector(p1, p2, {'q0': 'bad name', 'q1': 'in_B'}) with pytest.raises(InvalidMappingException): connector.resolve() # unknown port 'bad name' connector = ModeConnector(p1, p2, {'q0': 'in_A', 'bad name': 'in_B'}) with pytest.raises(InvalidMappingException): connector.resolve() # unknown port 'bad name' connector = ModeConnector(p1, p2, {'q0': 'in_A', 'q1': 'in_A'}) with pytest.raises(InvalidMappingException): connector.resolve() # duplicates connector = ModeConnector(p1, p2, {'q0': 'in_A'}) with pytest.raises(InvalidMappingException): connector.resolve() # mapping too small ================================================ FILE: tests/components/test_permutation.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import perceval as pcvl import perceval.components.unitary_components as comp from perceval.components import Circuit from perceval.components._decompose_perms import decompose_perms import perceval.algorithm as algo import numpy as np import pytest import random def test_permutation_3(): circuit = comp.PERM([2, 0, 1]) p = pcvl.Processor("SLOS", circuit) ca = algo.Analyzer(p, input_states=[pcvl.BasicState("|1,0,0>")], output_states="*") i_001 = ca.col(pcvl.BasicState("|0, 0, 1>")) expected_dist = np.zeros(3) expected_dist[i_001] = 1 assert np.allclose(ca.distribution, expected_dist) ca = algo.Analyzer(p, input_states=[pcvl.BasicState("|0,1,0>")], output_states="*") i_100 = ca.col(pcvl.BasicState("|1, 0, 0>")) expected_dist = np.zeros(3) expected_dist[i_100] = 1 assert np.allclose(ca.distribution, expected_dist) ca = algo.Analyzer(p, input_states=[pcvl.BasicState("|0,0,1>")], output_states="*") i_010 = ca.col(pcvl.BasicState("|0, 1, 0>")) expected_dist = np.zeros(3) expected_dist[i_010] = 1 assert np.allclose(ca.distribution, expected_dist) def test_permutation_inverse(): perm_vector = [4, 1, 3, 5, 2, 0] perm = comp.PERM(perm_vector) assert perm.perm_vector == perm_vector perm.inverse(h=True) assert perm.perm_vector == [perm_vector.index(i) for i in range(len(perm_vector))] perm.inverse(h=True) # Get back to the initial state assert perm.perm_vector == perm_vector perm.inverse(v=True) # Vertical inversion acts as if mode indexes were reversed: expected = [max(perm_vector)-i for i in perm_vector] expected.reverse() assert perm.perm_vector == expected @pytest.mark.parametrize("perm_list", [[2, 0, 1], [2, 3, 1, 0]]) def test_n_mode_permutation_in_2_mode_perms(perm_list): n_mode_perm = comp.PERM(perm_list) new_circ = n_mode_perm.break_in_2_mode_perms() for _, perm in new_circ: assert isinstance(perm, comp.PERM) assert perm.m == 2 assert np.all(n_mode_perm.compute_unitary() == new_circ.compute_unitary()) @pytest.mark.parametrize('ith_run', range(10)) def test_random_perm_breakup_run_multiple(ith_run): my_perm_list = list(range(15)) random.shuffle(my_perm_list) n_mode_perm = comp.PERM(my_perm_list) new_circ = n_mode_perm.break_in_2_mode_perms() for _, perm in new_circ: assert isinstance(perm, comp.PERM) assert perm.m == 2 assert np.all(n_mode_perm.compute_unitary() == new_circ.compute_unitary()) def test_circuit_decompose_perms(): # tests if any given circuit with n-mode perm returns a circuit with only 2-mode perms c = Circuit(3) // (0, comp.PERM([2, 0, 1])) // (0, comp.BS.H()) // (1, comp.PERM([1, 0])) decomp_c = decompose_perms(c) assert c.m == decomp_c.m for r, c in decomp_c: if isinstance(c, comp.PERM): assert c.m == 2 ================================================ FILE: tests/components/test_port.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval.components.port import Port, Herald, get_basic_state_from_ports from perceval.utils import Encoding, LogicalState, BasicState def test_basic_state_conversion(): ports = [Herald(1), Port(Encoding.DUAL_RAIL, "belle"), Port(Encoding.RAW, "bulle"), Herald(1), Herald(0), Port(Encoding.RAW, "rebelle"), Herald(1)] assert BasicState([0, 1, 0, 1]) == get_basic_state_from_ports(ports, LogicalState([1, 0, 1])) with pytest.raises(ValueError): get_basic_state_from_ports(ports, LogicalState([1, 0])) with pytest.raises(ValueError): get_basic_state_from_ports(ports, LogicalState([1, 0, 1, 0])) assert BasicState([1, 0, 1, 0, 1, 0, 1, 1]) == get_basic_state_from_ports( ports, LogicalState([1, 0, 1]), add_herald_and_ancillary=True) assert BasicState([1, 0, 0, 0]) == get_basic_state_from_ports(ports, LogicalState([0, 0, 0])) assert BasicState([1, 1, 0, 0, 1, 0, 0, 1]) == get_basic_state_from_ports( ports, LogicalState([0, 0, 0]), add_herald_and_ancillary=True) assert BasicState([0, 1, 1, 1]) == get_basic_state_from_ports(ports, LogicalState([1, 1, 1])) assert BasicState([1, 0, 1, 1, 1, 0, 1, 1]) == get_basic_state_from_ports( ports, LogicalState([1, 1, 1]), add_herald_and_ancillary=True) ================================================ FILE: tests/components/test_processor.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import numpy as np import pytest from unittest.mock import patch import perceval as pcvl from perceval import BSDistribution, FockState, NoisyFockState, Experiment, FFCircuitProvider from perceval.components import Circuit, Processor, BS, PS, catalog, UnavailableModeException, Port, PortLocation, \ PERM, Detector from perceval.utils import BasicState, StateVector, SVDistribution, Encoding, NoiseModel, P from perceval.backends import Clifford2017Backend from .._test_utils import LogChecker, assert_svd_close, assert_bsd_close @patch.object(pcvl.utils.logging.ExqaliburLogger, "warn") def test_processor_input_fock_state(mock_warn): p = Processor("Naive", Circuit(4)) # Init with perfect source with LogChecker(mock_warn, expected_log_number=0): p.with_input(FockState([0, 1, 1, 0])) assert_svd_close(p.source_distribution, SVDistribution({StateVector([0, 1, 1, 0]): 1})) def test_processor_input_fock_state_with_loss(): p = Processor("Naive", Circuit(4), NoiseModel(brightness=0.2)) p.with_input(FockState([0, 1, 1, 0])) expected = { StateVector([0, 1, 1, 0]): 0.04, StateVector([0, 1, 0, 0]): 0.16, StateVector([0, 0, 1, 0]): 0.16, StateVector([0, 0, 0, 0]): 0.64 } assert_svd_close(p.source_distribution, expected) def test_processor_input_fock_state_with_all_noise_sources(): nm = NoiseModel(brightness=0.2, indistinguishability=0.9, g2=0.1, g2_distinguishable=False) p = Processor("Naive", Circuit(4), nm) p.source.simplify_distribution = True p.with_input(FockState([0, 1, 1, 0])) expected = {'|0,0,0,0>': 16 / 25, '|0,0,2{0},0>': 0.0015490319977879558, '|0,0,{0},0>': 0.15836717690616972, '|0,0,{0}{1},0>': 8.37910960423266e-05, '|0,2{0},0,0>': 0.0015490319977879558, '|0,2{0},2{0},0>': 3.749218953392102e-06, '|0,2{0},{0},0>': 0.0003636359771584214, '|0,2{0},{0}{1},0>': 2.0280482640513694e-07, '|0,2{0},{1},0>': 1.96699985087703e-05, '|0,{0},0,0>': 0.15836717690616972, '|0,{0},2{0},0>': 0.0003636359771584214, '|0,{0},2{1},0>': 1.96699985087703e-05, '|0,{0},{0},0>': 0.03526897882672976, '|0,{0},{0}{1},0>': 1.96699985087703e-05, '|0,{0},{1},0>': 0.0039187754251921985, '|0,{0},{1}{2},0>': 1.0640004445062523e-06, '|0,{0}{1},0,0>': 8.37910960423266e-05, '|0,{0}{1},2{0},0>': 2.0280482640513694e-07, '|0,{0}{1},{0},0>': 1.96699985087703e-05, '|0,{0}{1},{0}{2},0>': 1.097023089996e-08, '|0,{0}{1},{2},0>': 1.0640004445062523e-06} result = {str(k): v for k, v in p.source_distribution.items()} assert pytest.approx(expected) == result assert pytest.approx(sum([v for v in p.source_distribution.values()])) == 1 def test_processor_input_state_vector(): p = Processor("Naive", Circuit(4)) # Init with perfect source sv = FockState([0, 1, 1, 0]) + FockState([1, 0, 0, 1]) p.with_input(sv) assert_svd_close(p.source_distribution, {sv: 1}) p = Processor("Naive", Circuit(4), noise=NoiseModel(transmittance=.4, g2=.06)) # Init with noise sv = FockState([0, 1, 1, 0]) + FockState([1, 0, 0, 1]) p.with_input(sv) assert_svd_close(p.source_distribution, {sv: 1}) # The source does NOT affect SV inputs def test_processor_probs(): qpu = Processor("Naive", BS()) qpu.with_input(FockState([1, 1])) # Are expected only states with 2 photons in the same mode. for m in range(qpu.circuit_size): qpu.add(m, Detector.threshold()) # With threshold detectors, the simulation will only detect |1,0> and |0,1> qpu.min_detected_photons_filter(2) probs = qpu.probs() # By default, all states are filtered and physical performance drops to 0 assert pytest.approx(probs['global_perf']) == 0 detectors = qpu.detectors for i in range(len(detectors)): detectors[i] = None # Ugly reset of detectors # With perfect detection, we get our results back probs = qpu.probs() bsd_out = probs['results'] assert pytest.approx(bsd_out[FockState("|2,0>")]) == 0.5 assert pytest.approx(bsd_out[FockState("|0,2>")]) == 0.5 assert pytest.approx(probs['global_perf']) == 1 def test_processor_samples(): proc = Processor(Clifford2017Backend(), BS()) # Without annotations proc.with_input(FockState("|1,1>")) samples = proc.samples(500) assert samples["results"].count(FockState([1, 1])) == 0 assert len(samples["results"]) == 500 # With annotations proc.with_input(StateVector(NoisyFockState("|{0},{1}>"))) samples = proc.samples(500) assert samples["results"].count(FockState([1, 1])) > 50 def test_processor_samples_max_shots(): p = Processor(Clifford2017Backend(), 4) # Identity circuit with perfect source p.with_input(FockState([1, 1, 1, 1])) for (max_samples, max_shots) in [(10, 10), (2, 50), (17, 2), (0, 11), (10, 0)]: # In this trivial case, 1 shot = 1 sample, so test this pair of parameters work as a dual threshold system samples = p.samples(max_samples, max_shots) assert len(samples['results']) == min(max_samples, max_shots) p = Processor(Clifford2017Backend(), 4) p.add(0, catalog['postprocessed cnot'].build_processor()) p.with_input(FockState([0, 1, 0, 1])) max_samples = 100 result_len = {} for max_shots in [100, 500, 2_000]: # Success prob = 1/9 --> AVG 900 shots to get 100 samples result_len[max_shots] = len(p.samples(max_samples, max_shots)['results']) assert result_len[100] < result_len[500] assert result_len[500] < result_len[2_000] assert result_len[2_000] == max_samples # 2k shots is enough to get the expected sample count def test_add_remove_ports(): processor = Processor("SLOS", 6) p0 = Port(Encoding.DUAL_RAIL, "q0") p1 = Port(Encoding.DUAL_RAIL, "q1") p2 = Port(Encoding.DUAL_RAIL, "q2") processor.add_port(0, p0, PortLocation.OUTPUT) processor.add_port(2, p1) processor.add_port(4, p2, PortLocation.INPUT) with pytest.raises(UnavailableModeException): processor.add_port(4, p2, PortLocation.INPUT) assert processor.in_port_names == ["", "", "q1", "q1", "q2", "q2"] assert processor.out_port_names == ["q0", "q0", "q1", "q1", "", ""] assert processor.get_input_port(0) is None assert processor.get_input_port(1) is None assert processor.get_input_port(2) is p1 assert processor.get_input_port(3) is p1 assert processor.get_input_port(4) is p2 assert processor.get_input_port(5) is p2 assert processor.get_output_port(0) is p0 assert processor.get_output_port(1) is p0 assert processor.get_output_port(2) is p1 assert processor.get_output_port(3) is p1 assert processor.get_output_port(4) is None assert processor.get_output_port(5) is None processor.remove_port(2, PortLocation.OUTPUT) with pytest.raises(UnavailableModeException): processor.remove_port(2, PortLocation.OUTPUT) with pytest.raises(UnavailableModeException): processor.add_port(2, p1) assert processor.in_port_names == ["", "", "q1", "q1", "q2", "q2"] assert processor.out_port_names == ["q0", "q0", "", "", "", ""] assert processor.get_input_port(0) is None assert processor.get_input_port(1) is None assert processor.get_input_port(2) is p1 assert processor.get_input_port(3) is p1 assert processor.get_input_port(4) is p2 assert processor.get_input_port(5) is p2 assert processor.get_output_port(0) is p0 assert processor.get_output_port(1) is p0 assert processor.get_output_port(2) is None assert processor.get_output_port(3) is None assert processor.get_output_port(4) is None assert processor.get_output_port(5) is None processor.remove_port(0, PortLocation.OUTPUT) processor.remove_port(2, PortLocation.INPUT) processor.remove_port(4, PortLocation.INPUT) with pytest.raises(UnavailableModeException): processor.remove_port(2, PortLocation.INPUT) for i in range(6): assert processor.get_input_port(i) is None assert processor.get_output_port(i) is None def test_phase_quantization(): nm = NoiseModel(phase_imprecision=0.1) p0 = Processor("SLOS", catalog["mzi phase first"].build_circuit(phi_a=0.596898191919898198, phi_b=0.16561561651616)) p1 = Processor("SLOS", catalog["mzi phase first"].build_circuit(phi_a=0.596898191919898198, phi_b=0.16561561651616), noise=nm) p2 = Processor("SLOS", catalog["mzi phase first"].build_circuit(phi_a=0.6, phi_b=0.2)) p0.with_input(FockState([1, 1])) p1.with_input(FockState([1, 1])) p2.with_input(FockState([1, 1])) with pytest.raises(AssertionError): assert_bsd_close(p0.probs()["results"], p1.probs()["results"]) assert_bsd_close(p1.probs()["results"], p2.probs()["results"]) p1.noise = NoiseModel() assert_bsd_close(p0.probs()["results"], p1.probs()["results"]) p0.noise = nm p1.noise = nm assert_bsd_close(p0.probs()["results"], p1.probs()["results"]) def test_phase_error(): error: float = 0.2 angle: float = 1 nm = NoiseModel(phase_error=error) p = Processor("SLOS", PS(phi=angle), noise=nm) c = p.linear_circuit() assert float(c._components[0][1].param("max_error")) == pytest.approx(error) a = c.compute_unitary()[0, 0] assert np.angle(a) != angle assert angle-error <= np.angle(a) <= angle+error # When a specific error is directly set on a phase shifter, the NoiseModel does not change the value specific_error: float = 0.3 p = Processor("SLOS", PS(phi=angle, max_error=specific_error), noise=nm) c = p.linear_circuit() assert float(c._components[0][1].param("max_error")) == pytest.approx(specific_error) a = c.compute_unitary()[0, 0] assert np.angle(a) != angle assert angle - specific_error <= np.angle(a) <= angle + specific_error # Works with symbols too symb_error = "err0" symb_angle = "phi0" p = Processor("SLOS", PS(phi=P(symb_angle), max_error=P(symb_error)), noise=nm) c = p.linear_circuit() err_param = c._components[0][1].param("max_error") assert err_param.is_variable and str(err_param.spv) == symb_error a = c.compute_unitary(use_symbolic=True)[0, 0] assert 'exp(I*' in str(a) and '*err0 + phi0' in str(a) def test_empty_output(): p = Processor("SLOS", 4) p.add(0, PERM([1, 0])) p.add_herald(0, 1) p.min_detected_photons_filter(2) p.with_input(FockState([0, 1, 0])) res = p.probs()["results"] assert res == BSDistribution() def test_mask_distinguishability(): p = Processor("SLOS", 3, noise=NoiseModel(indistinguishability=.5)) p.add_herald(1, 1) p.add_herald(2, 1) p.min_detected_photons_filter(0) input_state = FockState([0]) p.with_input(input_state) assert p.probs()["results"][input_state] == pytest.approx(1) def test_mask_detectors(): p = Processor("SLOS", 2) p.add(0, BS()) p.add(0, Detector.threshold()) p.add_herald(0, 1) # Since using a threshold, expected is at least 1 p.min_detected_photons_filter(0) p.with_input(FockState([1])) res = p.probs() assert res["results"][BasicState([0])] == pytest.approx(1) assert res["global_perf"] == pytest.approx(.5) p.compute_physical_logical_perf(True) res = p.probs() assert res["results"][BasicState([0])] == pytest.approx(1) assert res["physical_perf"] == pytest.approx(1) assert res["logical_perf"] == pytest.approx(.5) def test_min_photons_reset(): p = Processor("SLOS", 2, noise=NoiseModel(brightness=.5)) p.min_detected_photons_filter(2) input_state = FockState([1, 1]) p.with_input(input_state) res = p.probs() assert res["results"][input_state] == pytest.approx(1) assert res["global_perf"] == pytest.approx(.25) p.min_detected_photons_filter(0) res = p.probs() assert res["results"][input_state] == pytest.approx(.25) assert res["global_perf"] == pytest.approx(1) def test_flatten_processor(): ansatz = catalog["qloq ansatz"] group_sizes = [Encoding.QUDIT2, Encoding.QUDIT2] layers = ["Y"] phases = None p = ansatz.build_processor( group_sizes=group_sizes, layers=layers, phases=phases, ctype="cz" ) level_0_components = p.flatten(0) level_1_components = p.flatten(1) level_2_components = p.flatten(2) level_3_components = p.flatten(3) level_4_components = p.flatten(4) all_components = p.flatten() assert len(level_0_components) == 1 # 1 sub-circuit assert len(level_1_components) == 15 # 13 sub-sub-circuits (8 RYQUDIT2, 4 CZ2, 1 POSTPROCESSED CZ) + 2 PERM assert len(level_2_components) == 60 # 8*4 in RYQUDIT2, 4*5 in CZ2, 1*6 in POSTPROCESSED CZ + 2 PERM assert len(level_3_components) == 68 # 8*5 in RYQUDIT2, 4*5 in CZ2, 1*6 in POSTPROCESSED CZ + 2 PERM assert len(level_4_components) == 76 # 8*6 in RYQUDIT2, 4*5 in CZ2, 1*6 in POSTPROCESSED CZ + 2 PERM assert len(all_components) == len(level_4_components) def test_asymmetric_processor(): # Herald at left p = Processor("SLOS", 2) p.add(0, BS()) p.add_herald(0, 1, location=PortLocation.INPUT) assert p.m == 2 assert p.m_in == 1 assert p.heralds == {} assert p.in_heralds == {0: 1} p.with_input(FockState([0])) assert p.input_state == FockState([1, 0]) res = p.probs() assert res["results"][BasicState([1, 0])] == pytest.approx(0.5) assert res["results"][BasicState([0, 1])] == pytest.approx(0.5) # Herald at right p = Processor("SLOS", 2) p.add(0, BS()) p.add_herald(0, 1, location=PortLocation.OUTPUT) assert p.m == 1 assert p.m_in == 2 assert p.heralds == {0: 1} assert p.in_heralds == {} p.with_input(FockState([1, 0])) res = p.probs() assert res["results"][BasicState([0])] == pytest.approx(1) assert res["global_perf"] == pytest.approx(.5) def test_get_parameters(): e = Experiment(4) ffc = FFCircuitProvider(2, 0, BS(theta=P("theta0"))) ffc.add_configuration((1, 0), BS(theta=P("theta1"))) ffc.add_configuration((1, 0), Experiment(BS(theta=P("theta2")))) e.add(0, Detector.pnr()) e.add(1, Detector.pnr()) e.add(0, ffc) params = e.get_circuit_parameters() assert set(params.keys()) == {"theta0", "theta1", "theta2"} ================================================ FILE: tests/components/test_processor_composition.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest from perceval import PortLocation from perceval.components import (catalog, Circuit, BS, PS, PERM, Processor, Detector, UnavailableModeException, FFConfigurator, FFCircuitProvider, Unitary, Barrier) from perceval.utils import Matrix, P, LogicalState from perceval.runtime import RemoteProcessor from tests.runtime._mock_rpc_handler import get_rpc_handler_for_tests def test_processor_composition(): p = catalog['postprocessed cnot'].build_processor() # Circuit with [0,1] and [2,3] post-selection conditions p.add((0, 1), BS()) # Composing with a component on modes [0,1] should work with pytest.raises(AssertionError): p.add((1, 2), BS()) # Composing with a component on modes [1,2] should fail p_bs = Processor("SLOS", BS()) p.add((0, 1), p_bs) # Composing with a processor on modes [0,1] should work with pytest.raises(AssertionError): p.add((1, 2), p_bs) # Composing with a processor on modes [1,2] should fail def test_composition_error_post_selection(): processor = catalog['postprocessed cnot'].build_processor() # Composing 2 CNOTs on the exact same modes should work in theory, but not in the current implementation, # it's still possible to apply a PostSelect manually to the resulting Processor. with pytest.raises(AssertionError): processor.add(0, processor) processor2 = Processor("SLOS", 5) pp_cnot = catalog['postprocessed cnot'].build_processor() processor2.add(0, pp_cnot) # It's 100% valid that this 2nd case is blocked with pytest.raises(AssertionError): processor2.add(1, pp_cnot) def test_processor_composition_mismatch_modes(): # tests composing a smaller processor into larger one works # without breaking simplification (verifies it works with gates based circuits too) def sub_size_processor(): h_cnot = catalog['heralded cnot'].build_processor() p = Processor('SLOS', m_circuit=4, name='my_example') p.add(0, BS.H()) p.add(0, h_cnot) p.add(1, PS(math.pi / 4)) p.add(0, h_cnot) return p smaller_processor = sub_size_processor() p = Processor('SLOS', m_circuit=5, name='to_Which_i_add') p.add(0, smaller_processor) assert len(p.components) == 7 # 3 PERMs get added because heralds need to move r_list = [] comp_list = [] for r, c in p.components: r_list.append(r) comp_list.append(c) # checks order of components assert isinstance(comp_list[0], PERM) assert isinstance(comp_list[1], BS) assert isinstance(comp_list[2], Circuit) assert isinstance(comp_list[3], PS) assert isinstance(comp_list[4], PERM) assert isinstance(comp_list[5], Circuit) assert isinstance(comp_list[6], PERM) # checks the position of elements assert r_list[0] == [4, 5, 6] # checks PERM added here to move extra mode out of the way assert r_list[1][0] == 0 # BS added at mode 0 assert r_list[3][0] == 1 # checks PS at mode 1 def test_processor_add_detector(): p = Processor("SLOS", 4) p.add(0, Detector.pnr()) with pytest.raises(UnavailableModeException): p.add(0, PS(phi=0)) # Cannot add an optical component after a detector with pytest.raises(UnavailableModeException): p.add(0, Detector.pnr()) # Cannot add a detector after a detector def test_remote_processor_creation(): rp = RemoteProcessor(rpc_handler=get_rpc_handler_for_tests(), m=8) rp.add(0, BS()) def test_processor_composition_ports(): ls = LogicalState([0, 0]) cnot = catalog['postprocessed cnot'].build_processor() rp = RemoteProcessor(rpc_handler=get_rpc_handler_for_tests(), m=4) rp.min_detected_photons_filter(2) rp.add(0, cnot) rp.with_input(ls) # check that the input ports of the cnot are identical to the remote processor for mode in range(4): cnot_input_port = cnot.get_input_port(mode) rp_input_port = rp.get_input_port(mode) assert type(cnot_input_port) == type(rp_input_port) assert cnot_input_port.m == rp_input_port.m assert cnot_input_port.encoding == rp_input_port.encoding cnot_output_port = cnot.get_output_port(mode) rp_output_port = rp.get_output_port(mode) assert type(cnot_output_port) == type(rp_output_port) assert cnot_output_port.m == rp_output_port.m assert cnot_output_port.encoding == rp_output_port.encoding def test_processor_building_feed_forward(): m = 4 u = Unitary(Matrix.random_unitary(m), "U0") p = Processor("SLOS", u) ffm = FFCircuitProvider(1, 0, Unitary(Matrix.random_unitary(1)), name="D2") for i in range(m): with pytest.raises(UnavailableModeException): p.add(i, ffm) # Can't add because all modes are still photonic p.add(0, Detector.pnr()) # Mode 0 is now classical p.add(0, ffm) p.add(3, Detector.pnr()) # Mode 3 is now classical with pytest.raises(ValueError): p.add(3, ffm) # Can't add because controlled circuit would be out of the processor with pytest.raises(ValueError): p.add(-1, ffm) # Out of bound with pytest.raises(ValueError): p.add(4, ffm) # Out of bound def test_processor_feed_forward_multiple_layers(): m = 4 u = Unitary(Matrix.random_unitary(m), "U0") p = Processor("SLOS", u) p.add(2, Detector.pnr()) mzi = catalog["mzi phase last"].build_circuit() mzi.name = "U1" ffc = FFConfigurator(1, -1, mzi, {"phi_a": 0, "phi_b": 0}, name="D1") p.add(2, ffc) p.add(0, Detector.pnr()) p.add(1, Detector.pnr()) ffc2 = FFConfigurator(2, 1, PS(phi=P("phi")), {"phi": 1.57}, name="D2") p.add(0, ffc2) expected = (Unitary, Barrier, Barrier, Barrier, Detector, FFConfigurator, Barrier, Barrier, Detector, Detector, FFConfigurator) for (r, c), expected_type in zip(p.components, expected): assert isinstance(c, expected_type) def test_ff_controlled_circuit_size(): m = 4 u = Unitary(Matrix.random_unitary(m), "U0") p = Processor("SLOS", u) ffm = FFCircuitProvider(1, 0, Circuit(1), name="D2") ffm.add_configuration((1,), Circuit(2)) # Can add a larger circuit than the default one before it's used p.add(0, Detector.pnr()) p.add(0, ffm) with pytest.raises(RuntimeError): ffm.add_configuration((1,), Circuit(3)) def test_asymmetrical_composition(): p = Processor("SLOS", 3) p.add(0, BS()) p.add(1, BS()) p.add_herald(0, 0, location=PortLocation.OUTPUT) p.add_herald(2, 1, location=PortLocation.INPUT) p2 = Processor("SLOS", 3) p2.add(0, BS()) p2.add(1, BS()) p2.add_herald(0, 2, location=PortLocation.INPUT) p2.add_herald(2, 3, location=PortLocation.OUTPUT) p.add(1, p2) assert p.circuit_size == 4 assert p.in_heralds == {2: 1, 3: 2} assert p.heralds == {0: 0, 3: 3} def test_detector_composition(): detector_2 = Detector.ppnr(2) detector_3 = Detector.ppnr(3) detector_4 = Detector.ppnr(4) p = Processor("SLOS", 3) p.add(1, detector_2) p2 = Processor("SLOS", 2) p2.add(0, detector_3) p2.add(1, detector_4) p.add({2: 0, 0: 1}, p2) assert p.detectors == [detector_4, detector_2, detector_3] ================================================ FILE: tests/components/test_source.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import math from collections import Counter from perceval import filter_distribution_photon_count, SVDistribution, \ anonymize_annotations, FockState, NoisyFockState, StateVector from perceval.components import Source from perceval.utils.dist_metrics import tvd_dist from perceval.rendering.pdisplay import pdisplay_state_distrib from .._test_utils import strip_line_12, assert_svd_close, dict2svd def test_source_pure(): s = Source() svd = s.probability_distribution() assert strip_line_12(pdisplay_state_distrib(svd)) == strip_line_12(""" +-------+-------------+ | state | probability | +-------+-------------+ | |1> | 1 | +-------+-------------+""") assert_svd_close(svd, dict2svd({"|1>": 1})) def test_source_emission(): s = Source(emission_probability=0.4) svd = s.probability_distribution() assert_svd_close(svd, dict2svd({"|0>": 0.6, "|1>": 0.4})) def test_source_emission_g2(): s = Source(emission_probability=0.4, multiphoton_component=0.1, multiphoton_model="indistinguishable") svd = s.probability_distribution() assert_svd_close(svd, dict2svd({"|0>": 3/5, "|2>": 0.008336953374561418, "|1>": 0.391663})) def test_source_emission_g2_losses_indistinguishable(): s = Source(emission_probability=0.4, multiphoton_component=0.1, losses=0.08, indistinguishability=0.9, multiphoton_model="indistinguishable") svd = s.probability_distribution() assert_svd_close(svd, dict2svd({"|0>": 0.631386, "|2{0}>": 0.006694286297288383, "|{0}{1}>": 3.62111e-4, "|{0}>": 0.343035, "|{1}>": 0.01852243527847053})) def test_source_indistinguishability(): s = Source(indistinguishability=0.5) svd = s.probability_distribution() assert len(svd) == 2 for k, v in svd.items(): assert len(k) == 1 state = k[0] assert state.n == 1 assert isinstance(state, NoisyFockState) if str(state) != "|{0}>": assert pytest.approx(1-math.sqrt(0.5)) == v else: assert pytest.approx(math.sqrt(0.5)) == v def test_source_multiple_photons_per_mode(): s = Source() for nphotons in range(2,10): svd = s.probability_distribution(nphotons) assert_svd_close(svd, dict2svd({f"|{nphotons}>": 1})) ep = 0.41 s = Source(emission_probability=ep) svd = s.probability_distribution(2) assert_svd_close(svd, dict2svd({"|0>": (1-ep)**2, "|1>": ep*(1-ep)*2, "|2>": ep**2})) def test_source_sample_no_filter(): nb_samples = 200 bs = FockState("|1,1>") source_1 = Source(emission_probability=0.9, multiphoton_component=0.1, losses=0.1, indistinguishability=0.9) source_2 = Source(emission_probability=0.9, multiphoton_component=0.1, losses=0.1, indistinguishability=0.9) source_2.simplify_distribution = True # generate samples directly from the source samples_from_source = source_1.generate_samples(nb_samples, bs) assert len(samples_from_source) == nb_samples counter_samples = Counter(samples_from_source) total = sum(counter_samples.values()) dist_samples = SVDistribution() for k, v in counter_samples.items(): dist_samples.add(StateVector(k), v / total) # compare these samples with complete distribution dist_raw = source_2.generate_distribution(bs,0) dist = SVDistribution() for k, v in dist_raw.items(): dist.add(StateVector(NoisyFockState(k[0])), v) # just avoid the warning in tvd_dist for el in set(dist_samples.keys()) - set(dist.keys()): dist[el] = 0 for el in set(dist.keys()) - set(dist_samples.keys()): dist_samples[el] = 0 tvd = tvd_dist(dist_samples, dist) assert tvd == pytest.approx(0.0, abs=0.15) # total variation between two distributions is less than 0.15 # number of photons in samples nb_1p = 0 nb_2p = 0 nb_3p = 0 for sample in samples_from_source: if sample.n == 1: nb_1p += 1 elif sample.n == 2: nb_2p += 1 elif sample.n == 3: nb_3p += 1 assert nb_2p > nb_1p assert nb_2p > nb_3p assert nb_1p > nb_3p @pytest.mark.parametrize("brightness", [1, 0.7]) @pytest.mark.parametrize("g2", [0, 0.3]) @pytest.mark.parametrize("hom", [1, 0.6, 0]) @pytest.mark.parametrize("losses", [0, 0.4]) @pytest.mark.parametrize("multiphoton_model", ['distinguishable', 'indistinguishable']) def test_source_samples_with_filter(brightness, g2, hom, losses, multiphoton_model): nb_samples = 200 min_detected_photons = 2 bs = FockState("|1,1>") source_1 = Source(brightness, g2, hom, losses) source_2 = Source(brightness, g2, hom, losses) # generate samples directly from the source samples_from_source = source_1.generate_samples(nb_samples, bs, min_detected_photons) assert len(samples_from_source) == nb_samples assert all(bs.n >= min_detected_photons for bs in samples_from_source) counter_samples = Counter(samples_from_source) total = sum(counter_samples.values()) dist_samples = SVDistribution() for k, v in counter_samples.items(): dist_samples.add(StateVector(k), v / total) dist_samples = anonymize_annotations(dist_samples, annot_tag="_") # to be able to compare the distributions # compare these samples with complete distribution source_2.simplify_distribution = True dist = source_2.generate_distribution(bs, 0) dist_raw = filter_distribution_photon_count(dist, min_detected_photons)[0] dist = SVDistribution() for k, v in dist_raw.items(): dist.add(StateVector(NoisyFockState(k[0])), v) # just avoid the warning in tvd_dist for el in set(dist_samples.keys()) - set(dist.keys()): dist[el] = 0 for el in set(dist.keys()) - set(dist_samples.keys()): dist_samples[el] = 0 tvd = tvd_dist(dist_samples, dist) assert tvd == pytest.approx(0.0, abs=0.15) # total variation between two distributions is less than 0.15 # number of photons in samples nb_1p = 0 nb_2p = 0 nb_3p = 0 for sample in samples_from_source: if sample.n == 1: nb_1p += 1 elif sample.n == 2: nb_2p += 1 elif sample.n == 3: nb_3p += 1 assert nb_1p == 0 assert nb_2p > nb_3p ================================================ FILE: tests/components/test_time_delay.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval import Processor, BS, TD, FockState, BSDistribution from .._test_utils import assert_bsd_close p = Processor("SLOS", 2) p.add(0, BS()) p.add(0, TD(1)) p.add(0, BS()) def test_without_herald(): p.with_input(FockState([1, 0])) p.min_detected_photons_filter(0) expected = BSDistribution() expected[FockState([0, 0])] = 0.25 expected[FockState([1, 0])] = 0.25 expected[FockState([0, 1])] = 0.25 expected[FockState([2, 0])] = 0.125 expected[FockState([0, 2])] = 0.125 assert_bsd_close(p.probs()["results"], expected) def test_with_selection(): p.with_input(FockState([1, 0])) p.min_detected_photons_filter(1) expected = BSDistribution() expected[FockState([1, 0])] = 1/3 expected[FockState([0, 1])] = 1/3 expected[FockState([2, 0])] = 1/6 expected[FockState([0, 2])] = 1/6 expected_p_perf = 3/4 res = p.probs() assert_bsd_close(p.probs()["results"], expected) assert pytest.approx(res["global_perf"]) == expected_p_perf, "Wrong physical performance with time delays" def test_with_heralds(): p.add_herald(1, 0) p.with_input(FockState([1])) p.min_detected_photons_filter(0) expected = BSDistribution() expected[FockState([0])] = 0.4 expected[FockState([1])] = 0.4 expected[FockState([2])] = 0.2 expected_l_perf = 5/8 res = p.probs() assert_bsd_close(p.probs()["results"], expected) assert pytest.approx(res["global_perf"]) == expected_l_perf, "Wrong logical performance with time delays" ================================================ FILE: tests/components/test_transfer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest from pathlib import Path from perceval import Circuit, P, Matrix, GenericInterferometer, InterferometerShape, catalog import perceval.components.unitary_components as comp TEST_DATA_DIR = Path(__file__).resolve().parent.parent / 'data' def test_basic_transfer(): a = comp.BS() theta = P("theta") b = comp.BS(theta=theta) b.transfer_from(a) assert theta.defined and pytest.approx(math.pi/2) == float(theta) def test_basic_transfer_fix(): a = comp.BS(phi_tr=0.1) theta = P("theta") b = comp.BS(theta=theta) with pytest.raises(ValueError): b.transfer_from(a) # By default, you cannot transfer parameter values when other parameter value differ b.transfer_from(a, force=True) # Unless you force it assert pytest.approx(0.1) == float(b.param("phi_tr")) def test_transfer_complex_1(): a = Circuit(3) // (0, comp.PS(0)) // (2, comp.PS(math.pi/2)) // (0, comp.BS()) phi_a = P("phi_a") phi_b = P("phi_b") theta = P("theta") b = Circuit(3) // (0, comp.PS(phi_a)) // (2, comp.PS(phi_b)) // (0, comp.BS(theta=theta)) b.transfer_from(a) assert pytest.approx(0) == float(phi_a) assert pytest.approx(math.pi/2) == float(phi_b) assert pytest.approx(math.pi/2) == float(theta) def test_transfer_complex_2(): # order is not important a = Circuit(3) // (0, comp.PS(0)) // (2, comp.PS(math.pi/2)) // (0, comp.BS()) phi_a = P("phi_a") phi_b = P("phi_b") theta = P("theta") b = Circuit(3) // (2, comp.PS(phi_b)) // (0, comp.PS(phi_a)) // (0, comp.BS(theta=theta)) b.transfer_from(a) assert pytest.approx(0) == float(phi_a) assert pytest.approx(math.pi/2) == float(phi_b) assert pytest.approx(math.pi/2) == float(theta) def test_transfer_complex_3(): # the circuit can be bigger a = Circuit(3) // (0, comp.PS(0)) // (2, comp.PS(math.pi/2)) // (0, comp.BS()) // (1, comp.PS(0)) phi_a = P("phi_a") phi_b = P("phi_b") theta = P("theta") b = Circuit(3) // (2, comp.PS(phi_b)) // (0, comp.PS(phi_a)) // (0, comp.BS(theta=theta)) b.transfer_from(a) assert pytest.approx(0) == float(phi_a) assert pytest.approx(math.pi/2) == float(phi_b) assert pytest.approx(math.pi/2) == float(theta) def test_transfer_complex_4(): # but the circuit cannot match a component that is not here a = Circuit(3) // (0, comp.PS(0)) // (2, comp.PS(math.pi/2)) // (0, comp.BS()) // (1, comp.PS(0)) phi_a = P("phi_a") phi_b = P("phi_b") phi_c = P("phi_c") theta = P("theta") b = Circuit(3) // (2, comp.PS(phi_b)) // (1, comp.PS(phi_c)) // (0, comp.PS(phi_a)) // (0, comp.BS(theta=theta)) with pytest.raises(AssertionError): b.transfer_from(a) a = Circuit(3) // (0, comp.PS(0)) // (1, comp.PS(0)) // (2, comp.PS(math.pi/2)) // (0, comp.BS()) phi_a = P("phi_a") phi_b = P("phi_b") phi_c = P("phi_c") theta = P("theta") b = Circuit(3) // (2, comp.PS(phi_b)) // (0, comp.PS(phi_a)) // (0, comp.BS(theta=theta)) // (1, comp.PS(phi_c)) with pytest.raises(AssertionError): b.transfer_from(a) @pytest.mark.long_test def test_transfer_complex_5(): with open(TEST_DATA_DIR / 'u_random_8', "r") as f: m = Matrix(f) mzi = catalog["mzi phase last"].build_circuit() // comp.Barrier(2, visible=False) c1 = Circuit.decomposition(m, mzi, shape=InterferometerShape.TRIANGLE) c2 = GenericInterferometer(8, catalog["mzi phase last"].generate, shape=InterferometerShape.TRIANGLE) try: c2.transfer_from(c1) except Exception as ex: pytest.fail(f"Could not transfer to same architecture (force=False), because: {ex}") def mzi_with_variable_theta_generator(idx: int): return catalog["mzi phase last"].build_circuit( theta_a=f"theta{2*idx}", theta_b=f"theta{2*idx+1}", phi_a=0, phi_b=0) c3 = GenericInterferometer(8, mzi_with_variable_theta_generator, shape=InterferometerShape.TRIANGLE) try: c3.transfer_from(c1, force=True) # Need to force transfer as, here, the thetas are variable instead of phases except Exception as ex: pytest.fail(f"Could not transfer to same architecture (force=True), because: {ex}") ================================================ FILE: tests/components/test_unitary_determinant.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import perceval as pcvl import perceval.components.unitary_components as comp def test_determinant_base(): c = comp.BS() assert abs(c.U.det().simplify()) == 1 def test_determinant_generic(): c = comp.BS(theta=pcvl.P("θ"), phi_tl=pcvl.P("phi_tl"), phi_bl=pcvl.P("phi_bl"), phi_tr=pcvl.P("phi_tr")) assert abs(c.U.det().simplify()) == 1 def test_determinant_1(): c = comp.BS(theta=pcvl.P("θ"), phi_tl=math.pi/2, phi_bl=math.pi/2, phi_tr=0) assert abs(c.U.det().simplify()) == 1 def test_determinant_2(): c = comp.BS(theta=pcvl.P("θ"), phi_tl=math.pi/2, phi_bl=math.pi/2, phi_tr=math.pi/2) assert abs(c.U.det().simplify()) == 1 ================================================ FILE: tests/data/u_hom ================================================ # just a comment 0.70710678 -0.70710678 0.70710678 0.70710678 ================================================ FILE: tests/data/u_hom_sym ================================================ # just a comment, and 2-D matrix with visual brackets ⎡1/sqrt(2) -1/sqrt(2)⎤ ⎣1/sqrt(2) 1/sqrt(2)⎦ ================================================ FILE: tests/data/u_random_2 ================================================ (0.1927223296802083+0.3774169355491895j) (0.9050571662600762+0.0358620440657329j) (0.1284876072379235+0.8966077710959038j) (-0.39519098632506894-0.15300171216077074j) ================================================ FILE: tests/data/u_random_3 ================================================ (0.33084118075970004+0.05290112270710123j) (-0.5971151794174963+0.686412781443129j) (-0.1868025222730666+0.1585602660914611j) (0.19305588121394204+0.8195151473782099j) (0.38201132916175323+0.04865471145247938j) (-0.3523683524649537+0.13660511547178678j) (0.4172123460172797-0.06930939768164959j) (0.12849530801088693+0.08648685013025051j) (-0.12413219707050278-0.8841551017485278j) ================================================ FILE: tests/data/u_random_8 ================================================ (-0.3233639242889934+0.10358205117585266j) (-0.37202202897257847-0.1720808377972351j) (-0.5591329296583039-0.4120032780016657j) (-0.16466059374660816+0.07506418177803058j) (-0.31269541143334045+0.00011503277669029643j) (0.2666693962805691+0.10709521924168122j) (0.09738941925507738-0.07015613979051517j) (-0.0819392547999217-0.009446632308900724j) (-0.7329780875739519-0.16340889422443144j) (-0.2170728267927522+0.13470889670135858j) (0.06823051193601906+0.0717451575489616j) (0.04347204168170957+0.2079373470135595j) (0.28730213550845135+1.6354128166279347e-07j) (-0.31564768220248995-0.044882616599631844j) (-0.35186255673337097+0.077295598014435j) (0.0037478076028586785-0.04308930696980347j) (0.054796955185079196-0.1245886403150582j) (0.22268078612121078+0.0926787379994543j) (-0.0207373820149806-0.1637296582555161j) (0.3900387386213791+0.5409952488785299j) (-0.38093687357675754+0.19483980271473705j) (-0.04620756413372213+0.14783545465546757j) (-0.08531759694440418+0.04657725892117337j) (0.467422790458356-0.1275023805142013j) (0.10286409143480879-0.346043898775811j) (0.25168340891874647+0.5798209602891102j) (-0.18901984986224168-0.19281036986675587j) (-0.10016873885641789-0.00018431212848155076j) (0.0464165561926637+0.09793403816193341j) (0.13355916886151653+0.29916459909881904j) (-0.16462138909459445+0.18463137434943544j) (-0.4530785252677634+0.04056333630019522j) (0.1855755753526122-0.2478446521401129j) (-0.09227904155950076+0.000318017537288183j) (-0.4444018102785217+0.060702897208084236j) (0.4059230350941156+0.05530691404518641j) (0.10930125061755878-0.5748460766779978j) (-0.3598284257399992-0.05939219258292274j) (0.2059997069919364-0.010037548300152133j) (-0.0831527812304372+0.0419441923311935j) (0.023757057563371792-0.07273677928709153j) (0.04064503578234638-0.16246590305996006j) (-0.14333811102051738+0.36008485678240654j) (-0.453465559664984+0.24632943047736194j) (0.07143930090129413+0.058201888133935356j) (-0.18436498244283123+0.328885035212151j) (0.33423908294867044+0.2986272744338818j) (0.025514620805307885-0.44428571682335255j) (0.16304910748777435+0.18549546797648353j) (0.2175799275869488-0.4595969234289358j) (-0.1509919673622308+0.11915686357222556j) (-0.022987894303894314+0.1676878601645415j) (-0.06672409002547589-0.07847412191634096j) (-0.11628812600635013+0.18129922266326953j) (-0.6531927980922789-0.1602326087812177j) (-0.32351088167022035-0.028182078951379468j) (-0.08010038838443503-0.05258132623732395j) (0.09541556032725569+0.06906904976742811j) (0.11839678903087078+0.07266121565937811j) (-0.04350574697727824+0.051016003435305754j) (-0.39963987556429764-0.3219470384718944j) (0.21001941110592423-0.5646077734988887j) (-0.1088377275608185+0.2785003637790229j) (-0.21490226895445394-0.4373883047785115j) ================================================ FILE: tests/error_mitigation/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/error_mitigation/test_loss_mitigation.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from .._test_utils import retry from perceval.error_mitigation import photon_recycling from perceval.utils import BasicState, BSDistribution from perceval.components import catalog, Unitary from perceval.utils import Matrix, NoiseModel from perceval.algorithm import Sampler from perceval import Processor, Detector from perceval.utils.dist_metrics import tvd_dist, kl_divergence def _sampler_setup_cnot(output_type: str): # Processor config processor = Processor("SLOS", noise=NoiseModel(transmittance=0.3)) processor.min_detected_photons_filter(0) for m in range(processor.circuit_size): processor.add(m, Detector.threshold()) # Circuit circ = catalog['heralded cnot'].build_circuit() processor.set_circuit(circ) # Input state input_state = BasicState([0, 1, 0, 1, 1, 1]) processor.with_input(input_state) # Sampler sampler = Sampler(processor) if output_type == 'samples': return sampler.sample_count(20000)['results'] elif output_type == 'probs': return sampler.probs()['results'] @pytest.mark.parametrize('result_type', ['probs', 'samples']) def test_photon_loss_mitigation(result_type): lossy_sampling = _sampler_setup_cnot(output_type=result_type) ideal_photon_count = 4 mitigated_dist = photon_recycling(lossy_sampling, ideal_photon_count) for keys, value in mitigated_dist.items(): assert sum(keys) == ideal_photon_count assert value > 1e-6 def test_input_validation_loss_mitigation(): bs1 = BasicState([1, 1, 1, 0, 0, 0, 0]) bs2 = BasicState([1, 0, 0, 1, 1, 0, 0]) bs3 = BasicState([1, 0, 0, 0, 0, 0, 0]) bs4 = BasicState([0, 0, 0, 1, 1, 0, 0]) with pytest.raises(TypeError): photon_recycling(bs1, 3) # perfect bsd perfect_bsd = BSDistribution() perfect_bsd[bs1] = 0.9 perfect_bsd[bs2] = 0.1 # missing n-1 state lossy_bsd1 = BSDistribution() lossy_bsd1[bs1] = 0.3333 lossy_bsd1[bs2] = 0.3333 lossy_bsd1[bs3] = 0.3333 # missing n-2 state lossy_bsd2 = BSDistribution() lossy_bsd2[bs1] = 0.3333 lossy_bsd2[bs2] = 0.3333 lossy_bsd2[bs4] = 0.3333 with pytest.raises(ValueError): photon_recycling(perfect_bsd, 3) with pytest.raises(ValueError): photon_recycling(lossy_bsd1, 3) with pytest.raises(ValueError): photon_recycling(lossy_bsd2, 3) with pytest.raises(ValueError): photon_recycling(lossy_bsd1, 6) # incorrect count with pytest.raises(ValueError): photon_recycling(lossy_bsd2, 6) def _compute_random_circ_probs(source_emission, num_photons): random_loc = Unitary(Matrix.random_unitary(20)) # Processor config processor = Processor("SLOS", random_loc, noise=NoiseModel(transmittance=source_emission)) processor.min_detected_photons_filter(0) for m in range(processor.circuit_size): processor.add(m, Detector.threshold()) # Input state input_state = BasicState([1] * num_photons + [0] * (random_loc.m - num_photons)) processor.with_input(input_state) # Sampler sampler = Sampler(processor) return sampler.probs()['results'] @pytest.mark.long_test @retry(AssertionError, 3) def test_mitigation_over_postselect_tvd(): ideal_photon_count = 4 # lossless distribution ideal_dist = _compute_random_circ_probs(source_emission=1, num_photons=ideal_photon_count) # lossy distribution lossy_dist = _compute_random_circ_probs(source_emission=0.3, num_photons=ideal_photon_count) # compute the mitigated distribution mitigated_dist = photon_recycling(lossy_dist, ideal_photon_count) # post-selected distribution post_select_dist = BSDistribution() for state, prob in lossy_dist.items(): if state.n == ideal_photon_count: post_select_dist.add(state, prob) post_select_dist.normalize() # TVD Metric tvd_miti = tvd_dist(ideal_dist, mitigated_dist) tvd_post = tvd_dist(ideal_dist, post_select_dist) assert tvd_miti < tvd_post # checks that mitigated is closer to ideal than post-selected distribution assert kl_divergence(ideal_dist, mitigated_dist) < kl_divergence(ideal_dist, post_select_dist) ================================================ FILE: tests/rendering/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/rendering/test_rendering.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from unittest.mock import patch from perceval.utils import PersistentData from perceval.rendering import DisplayConfig, PhysSkin, DebugSkin from perceval.rendering.circuit.display_config import _get_default_skin @patch.object(PersistentData, "load_config") @patch.object(PersistentData, "save_config") def test_display_config(mock_save_config, mock_load_config): mock_load_config.return_value = {} DisplayConfig.select_skin(_get_default_skin()) # Force the default skin to use the mock return value assert DisplayConfig._selected_skin == PhysSkin DisplayConfig.select_skin(DebugSkin) assert DisplayConfig._selected_skin == DebugSkin DisplayConfig.save() assert mock_save_config.call_args.args[0] == {'pdisplay': {'skin': 'DebugSkin'}} mock_load_config.return_value = {'pdisplay': {'skin': 'DebugSkin'}} DisplayConfig.select_skin(_get_default_skin()) # Force the default skin to use the mock return value assert DisplayConfig._selected_skin == DebugSkin ================================================ FILE: tests/rendering/test_visualization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import sys import sympy as sp from perceval import P, Experiment from perceval.components.compiled_circuit import CompiledCircuit from perceval.utils import Matrix, BasicState, Parameter, InterferometerShape from perceval.components import (BS, PS, PBS, WP, HWP, PERM, QWP, PR, Unitary, TD, Detector, catalog, FFConfigurator, FFCircuitProvider, Circuit, GenericInterferometer, Processor) from perceval.rendering.circuit import SymbSkin from .._test_utils import _save_or_check, save_figs # Avoid an arbitrary issue on windows-latest runner with TCL/Tk if sys.platform == 'win32': import matplotlib matplotlib.use('Agg') @pytest.mark.rendering def test_svg_dump_phys_bs(tmp_path, save_figs): _save_or_check(BS.H(), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_ps(tmp_path, save_figs): _save_or_check(PS(sp.pi / 2), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_pbs(tmp_path, save_figs): _save_or_check(PBS(), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_dt(tmp_path, save_figs): _save_or_check(TD(0), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_wp(tmp_path, save_figs): _save_or_check(WP(sp.pi / 4, sp.pi / 4), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_hwp(tmp_path, save_figs): _save_or_check(HWP(sp.pi / 2), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_qwp(tmp_path, save_figs): _save_or_check(QWP(sp.pi / 4), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_pr(tmp_path, save_figs): _save_or_check(PR(sp.pi / 4), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_perm4_0(tmp_path, save_figs): _save_or_check(Circuit(4) // PERM([0, 1, 2, 3]), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_perm4_inv(tmp_path, save_figs): _save_or_check(Circuit(4) // PERM([3, 2, 1, 0]), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_phys_perm4_swap(tmp_path, save_figs): _save_or_check(Circuit(4) // PERM([3, 1, 2, 0]), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_no_circuit_4(tmp_path, save_figs): _save_or_check(Circuit(4), tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_symb_bs_compact(tmp_path, save_figs): _save_or_check(BS(BS.r_to_theta(1/3)), tmp_path, sys._getframe().f_code.co_name, save_figs, compact=True, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_symb_bs_compact_false(tmp_path, save_figs): _save_or_check(BS(BS.r_to_theta(1/3)), tmp_path, sys._getframe().f_code.co_name, save_figs, compact=False, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_symb_ps(tmp_path, save_figs): _save_or_check(PS(sp.pi / 2), tmp_path, sys._getframe().f_code.co_name, save_figs, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_symb_pbs_compact(tmp_path, save_figs): _save_or_check(PBS(), tmp_path, sys._getframe().f_code.co_name, save_figs, compact=True, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_symb_pbs_compact_false(tmp_path, save_figs): _save_or_check(PBS(), tmp_path, sys._getframe().f_code.co_name, save_figs, compact=False, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_symb_pr(tmp_path, save_figs): _save_or_check(PR(sp.pi / 4), tmp_path, sys._getframe().f_code.co_name, save_figs, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_symb_wp(tmp_path, save_figs): _save_or_check(WP(sp.pi / 4, sp.pi / 4), tmp_path, sys._getframe().f_code.co_name, save_figs, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_symb_hwp(tmp_path, save_figs): _save_or_check(HWP(sp.pi / 2), tmp_path, sys._getframe().f_code.co_name, save_figs, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_symb_qwp(tmp_path, save_figs): _save_or_check(QWP(sp.pi / 4), tmp_path, sys._getframe().f_code.co_name, save_figs, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_phys_multi_perm(tmp_path, save_figs): nc = (Circuit(4) .add((0, 1), PERM([1, 0])) .add((1, 2), PERM([1, 0])) .add((2, 3), PERM([1, 0])) .add((1, 2), PERM([1, 0])) .add((0, 1), PERM([1, 0]))) _save_or_check(nc, tmp_path, sys._getframe().f_code.co_name, save_figs) def _create_qrng(): chip_qrng = Circuit(4, name='QRNG') # Parameters phis = [Parameter("phi1"), Parameter("phi2"), Parameter("phi3"), Parameter("phi4")] return (chip_qrng .add((0, 1), BS()) .add((2, 3), BS()) .add((1, 2), PERM([1, 0])) .add(0, PS(phis[0])) .add(2, PS(phis[2])) .add((0, 1), BS()) .add((2, 3), BS()) .add(0, PS(phis[1])) .add(2, PS(phis[3])) .add((0, 1), BS()) .add((2, 3), BS()) ) @pytest.mark.rendering def test_svg_dump_qrng(tmp_path, save_figs): c = _create_qrng() _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs, compact=False, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_qrng_compact(tmp_path, save_figs): c = _create_qrng() _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs, compact=True, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_phys_universal1(tmp_path, save_figs): ub1 = Circuit(2) // BS.H() // (0, PS(Parameter("θ"))) // BS.H() // (0, PS(Parameter("φ"))) _save_or_check(ub1, tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_unitary(tmp_path, save_figs): m = 6 c_a = Unitary(name="W_1", U=Matrix.random_unitary(m)) c_b = Unitary(name="W_2", U=Matrix.random_unitary(m)) p_x = Parameter("x") c = (Circuit(m) .add(0, c_a, merge=False) .add(0, PS(p_x)) .add(0, c_b, merge=False)) _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_grover(tmp_path, save_figs): def oracle(mark): """Values 0, 1, 2 and 3 for parameter 'mark' respectively mark the elements "00", "01", "10" and "11" of the list.""" oracle_circuit = Circuit(m=2, name='Oracle') # The following dictionary translates n into the corresponding component settings oracle_dict = {0: (1, 0), 1: (0, 1), 2: (1, 1), 3: (0, 0)} PC_state, LC_state = oracle_dict[mark] # Mode b if PC_state == 1: oracle_circuit.add(0, _HWP(0), merge=True) oracle_circuit.add(0, PR(sp.pi / 2)) if LC_state == 1: oracle_circuit.add(0, _HWP(0), merge=True) # Mode a if LC_state == 1: oracle_circuit.add(1, _HWP(0), merge=True) if PC_state == 1: oracle_circuit.add(1, _HWP(0), merge=True) return oracle_circuit def _HWP(xsi): hwp = Circuit(m=1) // HWP(xsi) // PS(-sp.pi / 2) return hwp bs = BS.H(phi_tr=sp.pi / 2, phi_tl=-sp.pi / 2) init_circuit = Circuit(m=2, name="Initialization") init_circuit.add(0, _HWP(sp.pi / 8), merge=True) init_circuit.add((0, 1), bs) init_circuit.add(0, PS(-sp.pi)) inversion_circuit = Circuit(m=2, name='Inversion') inversion_circuit.add((0, 1), bs) inversion_circuit.add(0, _HWP(sp.pi / 4), merge=True) inversion_circuit.add((0, 1), bs) detection_circuit = Circuit(m=4, name='Detection') detection_circuit.add((0, 1), PBS()) detection_circuit.add((2, 3), PBS()) grover_circuit = Circuit(m=2, name='Grover') grover_circuit.add((0, 1), init_circuit).add((0, 1), oracle(0)).add((0, 1), inversion_circuit) _save_or_check(grover_circuit, tmp_path, sys._getframe().f_code.co_name + "-rec", save_figs, recursive=True) _save_or_check(grover_circuit, tmp_path, sys._getframe().f_code.co_name + "-norec", save_figs, recursive=False) @pytest.mark.rendering def test_svg_bs_based_generic_no_phase_rectangle(tmp_path, save_figs): c = GenericInterferometer(5, fun_gen=lambda idx: BS.H() // PS(Parameter("phi_%d" % idx)), shape=InterferometerShape.RECTANGLE) _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_bs_based_generic_with_phase_rectangle(tmp_path, save_figs): c = GenericInterferometer(5, fun_gen=lambda idx: BS.H() // PS(Parameter("phi_%d" % idx)), shape=InterferometerShape.RECTANGLE, depth=10, phase_shifter_fun_gen=lambda idx: PS(Parameter("theta_%d" % idx))) _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_mzi_based_generic_triangle(tmp_path, save_figs): c = GenericInterferometer(5, fun_gen=lambda idx: BS.H() // PS(Parameter("phi_%d" % idx)), shape=InterferometerShape.TRIANGLE, phase_shifter_fun_gen=lambda idx: PS(Parameter("theta_%d" % idx))) _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_decomposition_symb_compact(tmp_path, save_figs): c1 = Circuit.decomposition(PERM([3, 1, 0, 2]).compute_unitary(), BS(theta=Parameter("theta"))) _save_or_check(c1, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True, compact=True, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_processor_with_heralds_phys(tmp_path, save_figs): p = catalog['klm cnot'].build_processor() c = Circuit(2, "Test circuit") // BS() // PS(0.3) // BS() pc = Processor('SLOS', c) pc.add_herald(1, 0) p.add(2, pc) _save_or_check(p, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_processor_with_heralds_phys_not_recursive(tmp_path, save_figs): p = catalog['klm cnot'].build_processor() c = Circuit(2, "Test circuit") // BS() // PS(0.3) // BS() pc = Processor('SLOS', c) pc.add_herald(1, 0) p.add(2, pc) _save_or_check(p, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=False) @pytest.mark.rendering def test_svg_processor_with_heralds_margin_overflow_left_phys(tmp_path, save_figs): c = Circuit(3) // BS() // (1, BS()) pc = Processor('SLOS', c) pc.add_herald(0, 0) _save_or_check(pc, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_processor_with_heralds_margin_overflow_right_phys(tmp_path, save_figs): c = Circuit(4) // (1, BS()) // BS() pc = Processor('SLOS', c) pc.add_herald(0, 0) pc.add_herald(2, 1) _save_or_check(pc, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_processor_with_heralds_margin_overflow_left_right_phys(tmp_path, save_figs): c = Circuit(2) // BS() pc = Processor('SLOS', c) pc.add_herald(0, 0) _save_or_check(pc, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_processor_with_heralds_perm_following_phys(tmp_path, save_figs): c = Circuit(4) // (1, PERM([1, 0])) // (1, BS()) // (0, PERM([1, 0])) // BS() // (1, PERM([1, 0])) pc = Processor('SLOS', c) pc.add_herald(0, 0) pc.add_herald(2, 1) _save_or_check(pc, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_processor_with_heralds_and_barriers_phys(tmp_path, save_figs): c = Circuit(4) // (1, PERM([1, 0])) @ (1, BS()) // (0, PERM([1, 0])) // BS() @ (1, PERM([1, 0])) pc = Processor('SLOS', c) pc.add_herald(0, 0) pc.add_herald(2, 1) pc.add(0, Detector.pnr()) pc.add(1, Detector.ppnr(0.5)) pc.add(2, Detector.threshold()) _save_or_check(pc, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_dump_barrier_phys(tmp_path, save_figs): c = Circuit(4) // BS() @ (2, BS()) // (1, BS()) @ BS() _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_dump_barrier_symb(tmp_path, save_figs): c = Circuit(4) // BS() @ (2, BS()) // (1, BS()) @ BS() _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True, skin_type=SymbSkin) @pytest.mark.rendering @pytest.mark.parametrize("merge_pre_MZI", [False, True]) @pytest.mark.parametrize("merge_upper_MZI", [False, True]) @pytest.mark.parametrize("merge_lower_MZI", [False, True]) def test_svg_dump_circuit_box_bell_state(tmp_path, save_figs, merge_pre_MZI, merge_upper_MZI, merge_lower_MZI): pre_MZI = (Circuit(4, name="Bell State Prep") .add(0, BS()) .add(2, BS()) .add(1, PERM([1, 0]))) upper_MZI = (Circuit(2, name="upper MZI") .add(0, PS(phi=Parameter('phi_0'))) .add(0, BS()) .add(0, PS(phi=Parameter('phi_2'))) .add(0, BS())) lower_MZI = (Circuit(2, name="lower MZI") .add(0, PS(phi=Parameter('phi_1'))) .add(0, BS()) .add(0, PS(phi=Parameter('phi_3'))) .add(0, BS())) chip = (Circuit(4) .add(0, pre_MZI, merge=merge_pre_MZI) .add(0, upper_MZI, merge=merge_upper_MZI) .add(2, lower_MZI, merge=merge_lower_MZI)) processor = Processor('SLOS', chip) fig_name = sys._getframe().f_code.co_name fig_name += "T" if merge_pre_MZI else "F" fig_name += "T" if merge_upper_MZI else "F" fig_name += "T" if merge_lower_MZI else "F" _save_or_check(c=processor, tmp_path=tmp_path, circuit_name=fig_name, save_figs=save_figs, recursive=True) def create_processor_with_feed_forward(): u = Unitary(Matrix.random_unitary(4), "U0") p = Processor("SLOS", u) mzi = catalog["mzi phase last"].build_circuit() mzi.name = "U1" ffc1 = FFConfigurator(1, -1, mzi, {"phi_a": 0, "phi_b": 0}, name="D1") p.add(2, Detector.pnr()) p.add(2, ffc1) p.add(0, Detector.pnr()) p.add(1, Detector.pnr()) ffc2 = FFCircuitProvider(2, 1, Unitary(Matrix.random_unitary(1)), name="D2") p.add(0, ffc2) p.with_input(BasicState([1, 0, 1, 0])) return p @pytest.mark.rendering def test_svg_dump_feed_forward_symb(tmp_path, save_figs): p = create_processor_with_feed_forward() _save_or_check(p, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_feed_forward_phys(tmp_path, save_figs): p = create_processor_with_feed_forward() _save_or_check(p, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_dump_feed_forward_phys_norec(tmp_path, save_figs): p = create_processor_with_feed_forward() _save_or_check(p, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=False) @pytest.mark.rendering def test_svg_dump_compiled_circuit_no_template(tmp_path, save_figs): c = CompiledCircuit("Ascella", 2, [1.23]) _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_compiled_circuit_no_template_symb(tmp_path, save_figs): c = CompiledCircuit("Ascella", 2, [1.23]) _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs, skin_type=SymbSkin) @pytest.mark.rendering def test_svg_dump_compiled_circuit_with_template(tmp_path, save_figs): template = Circuit(2).add(0, BS()).add(0, PS(P("phi"))).add(0, BS()) c = CompiledCircuit("Ascella", template, [1.23]) _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs) @pytest.mark.rendering def test_svg_dump_compiled_circuit_with_template_recursive(tmp_path, save_figs): template = Circuit(2).add(0, BS()).add(0, PS(P("phi"))).add(0, BS()) c = CompiledCircuit("Ascella", template, [1.23]) _save_or_check(c, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) @pytest.mark.rendering def test_svg_dump_compiled_circuit_with_template_experiment(tmp_path, save_figs): template = Circuit(2).add(0, BS()).add(0, PS(P("phi"))).add(0, BS()) c = CompiledCircuit("Ascella", template, [1.23]) e = Experiment(c) e.add(0, BS()) _save_or_check(e, tmp_path, sys._getframe().f_code.co_name, save_figs, recursive=True) ================================================ FILE: tests/rendering/test_visualization_ide.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os os.environ["PYCHARM_HOSTED"] = "1" import perceval as pcvl import perceval.components.unitary_components as comp def test_ide_visualization(capfd): pcvl.pdisplay(comp.PS(0).definition()) out, err = capfd.readouterr() assert out.find("matrix") == -1 ================================================ FILE: tests/requirements.txt ================================================ pytest pytest-cov pytest-benchmark flake8 responses pytest-responses ================================================ FILE: tests/runtime/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/runtime/_mock_rpc_handler.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import annotations # Python 3.11 : Replace using Self typing import uuid import json import re import datetime from enum import Enum import requests import responses from perceval.runtime.rpc_handler import ( RPCHandler, _ENDPOINT_JOB_CANCEL, _ENDPOINT_JOB_CREATE, _ENDPOINT_JOB_RESULT, _ENDPOINT_JOB_STATUS, _ENDPOINT_PLATFORM_DETAILS, _ENDPOINT_PLATFORM_DETAILS_NEW, _PROCESSOR_SECTION, _ENDPOINT_JOB_RERUN, _JOB_ID_KEY, _ENDPOINT_JOB_AVAILABILITY, quote_plus ) from perceval.runtime.job_status import RunningStatus, JobStatus _TIMESTAMP = datetime.datetime.now().timestamp() DEFAULT_PLATFORM_INFO = { 'id': str(uuid.uuid4()), 'name': None, 'perfs': {}, 'specs': { 'available_commands': ['probs'], 'connected_input_modes': [0, 2, 4, 6, 8, 10], 'constraints': { 'max_mode_count': 20, 'max_photon_count': 6, 'min_mode_count': 1, 'min_photon_count': 1, }, 'specific_circuit': 'serialized circuit' }, 'status': 'available', 'type': 'simulator', } ARCHITECTURE_PLATFORM_INFO = { 'id': str(uuid.uuid4()), 'name': None, 'perfs': {}, 'specs': { 'available_commands': ['probs'], 'connected_input_modes': [0, 2, 4, 6, 8, 10], 'constraints': { 'max_mode_count': 20, 'max_photon_count': 6, 'min_mode_count': 1, 'min_photon_count': 1, }, 'architecture': 'serialized experiment' }, 'status': 'available', 'type': 'simulator', } UUID_REGEXP = "[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}" class CloudEndpoint(Enum): CreateJob = 0 JobStatus = 1 JobResults = 2 CancelJob = 3 RerunJob = 4 PlatformDetails = 5 @staticmethod def from_response(response: responses.Response) -> CloudEndpoint: if _ENDPOINT_JOB_STATUS in response.url: return CloudEndpoint.JobStatus if _ENDPOINT_JOB_RESULT in response.url: return CloudEndpoint.JobResults if _ENDPOINT_JOB_CANCEL in response.url: return CloudEndpoint.CancelJob if _ENDPOINT_JOB_RERUN in response.url: return CloudEndpoint.RerunJob if _ENDPOINT_JOB_CREATE in response.url: return CloudEndpoint.CreateJob if _ENDPOINT_PLATFORM_DETAILS in response.url: return CloudEndpoint.PlatformDetails if _ENDPOINT_PLATFORM_DETAILS_NEW in response.url: return CloudEndpoint.PlatformDetails class RPCHandlerResponsesBuilder(): """Build responses for rpc handler, act as a cloud mock. :param rpc_handler: rpc handler to mock :param platform_details: platform details that rpc_handler.fetch_platform_details will return :param default_job_status: status of the job that rpc_handler.create_job will produce. Default is SUCCESS """ def __init__(self, rpc_handler: RPCHandler, platform_details: dict = DEFAULT_PLATFORM_INFO, default_job_status: RunningStatus | None = RunningStatus.SUCCESS, authorized_retry=4, use_new_platform_details_url: bool = False) -> None: self._rpc_handler = rpc_handler platform_details['name'] = rpc_handler.name self._platform_info = platform_details self._job_status = default_job_status self._job_status_index = 0 self._job_status_sequence = [] self._authorized_retry = authorized_retry self._custom_status_response = None self._job_availability_response = {"max_jobs_in_queue": 1, "num_jobs_in_queue": 0} self.last_payload = {} responses.reset() self._set_default_responses(use_new_platform_details_url) def set_default_job_status(self, default_job_status: RunningStatus | None) -> None: """Set the status of the job that rpc_handler.create_job will produce. None means rpc_handler.create_job will return an error (400). :param default_job_status: status of the job that rpc_handler.create_job will produce. """ self._job_status = default_job_status def set_job_status_sequence(self, job_status_sequence: list[RunningStatus | None]) -> None: """Set the sequence of status of the jobs that rpc_handler.create_job will produce. None means rpc_handler.create_job will return an error (400). :param default_job_status: sequence of status of the jobs that rpc_handler.create_job will produce. """ self._job_status_sequence = job_status_sequence def _set_default_responses(self, use_new_platform_details_url: bool = False) -> None: self._set_get_platform_details_responses() if use_new_platform_details_url: self._set_get_new_platform_details_responses(200) else: self._set_get_new_platform_details_responses(404) self._set_job_availability_responses() for method, endpoint in [ ('POST', _ENDPOINT_JOB_RERUN), ('POST', _ENDPOINT_JOB_CANCEL), ('GET', _ENDPOINT_JOB_STATUS), ('GET', _ENDPOINT_JOB_RESULT) ]: responses.add(responses.Response( method=method, url=re.compile((self._rpc_handler.url + endpoint).replace('/', r"\/") + UUID_REGEXP), status=404)) responses.add_callback(responses.POST, self._rpc_handler.url + _ENDPOINT_JOB_CREATE, callback=self._create_job_callback) def _reset_default_responses(self) -> None: for method, endpoint in [ ('POST', _ENDPOINT_JOB_RERUN), ('POST', _ENDPOINT_JOB_CANCEL), ('GET', _ENDPOINT_JOB_STATUS), ('GET', _ENDPOINT_JOB_RESULT) ]: responses.remove(responses.Response( method=method, url=re.compile((self._rpc_handler.url + endpoint).replace('/', r"\/") + UUID_REGEXP), status=404)) responses.add(responses.Response( method=method, url=re.compile((self._rpc_handler.url + endpoint).replace('/', r"\/") + UUID_REGEXP), status=404)) def _get_job_status(self): if self._job_status_sequence: status = self._job_status_sequence[self._job_status_index] self._job_status_index += 1 if self._job_status_index == len(self._job_status_sequence): self._job_status_index = 0 return status return self._job_status def _create_job_callback(self, request: requests.PreparedRequest) -> tuple[int, dict, str]: self.last_payload = json.loads(request.body) if request.body else {} status = self._get_job_status() if status is None: return (400, {"content-type": "application/json"}, "") job_id = str(uuid.uuid4()) for _ in range(self._authorized_retry): self._set_rerun_job_responses(job_id, status) self._set_cancel_job_responses(job_id, status) self._set_job_status_responses(job_id, status) self._set_job_result_responses(job_id, status) self._reset_default_responses() return (200, {"content-type": "application/json"}, json.dumps({_JOB_ID_KEY: job_id})) def _set_rerun_job_responses(self, job_id: str, status: RunningStatus = RunningStatus.SUCCESS) -> None: job_status = JobStatus() job_status.status = status if job_status.failed: responses.add_callback( responses.POST, self._rpc_handler.url + _ENDPOINT_JOB_RERUN + job_id, callback=self._create_job_callback) else: responses.add(responses.Response( method='POST', url=self._rpc_handler.url + _ENDPOINT_JOB_RERUN + job_id, status=400)) def _set_cancel_job_responses(self, job_id: str, status: RunningStatus = RunningStatus.SUCCESS) -> None: job_status = JobStatus() job_status.status = status if job_status.running or job_status.waiting: responses.add(responses.Response( method='POST', url=self._rpc_handler.url + _ENDPOINT_JOB_CANCEL + job_id, status=200)) else: responses.add(responses.Response( method='POST', url=self._rpc_handler.url + _ENDPOINT_JOB_CANCEL + job_id, status=400)) def get_job_status_response_body_from_job_status(self, status: RunningStatus) -> dict: response_body = { 'duration': None, 'progress': None, 'status': RunningStatus.to_server_response(status), 'creation_datetime': _TIMESTAMP, 'start_time': None, 'status_message': None } if status == RunningStatus.RUNNING: response_body['progress'] = 0.5 response_body['duration'] = 10 response_body['start_time'] = response_body['creation_datetime'] + 1. elif status == RunningStatus.SUCCESS: response_body['progress'] = 1.0 response_body['duration'] = 20 response_body['start_time'] = response_body['creation_datetime'] + 1. elif status == RunningStatus.CANCELED: response_body['status_message'] = 'Cancel requested from web interface' return response_body def _set_job_status_responses(self, job_id: str, status: RunningStatus = RunningStatus.SUCCESS) -> None: responses.add(responses.Response( method='GET', url=self._rpc_handler.url + _ENDPOINT_JOB_STATUS + job_id, status=200, json=self._custom_status_response if self._custom_status_response else self.get_job_status_response_body_from_job_status(status))) def set_job_status_custom_responses(self, response: json) -> None: self._custom_status_response = response def set_job_availability_count(self, count: int) -> None: self._reset_job_availability_responses() self._job_availability_response = {"max_jobs_in_queue": count, "num_jobs_in_queue": 0} self._set_job_availability_responses() def remove_job_status_custom_responses(self) -> None: self._custom_status_response = None def get_job_result_response_body_from_job_status(self, status: RunningStatus) -> dict: response_body = { 'results': None } if status == RunningStatus.SUCCESS: response_body['results'] = json.dumps( {'results': ':PCVL:BasicState:|>', 'logical_perf': 1, 'physical_perf': 0.1}) return response_body def _set_job_result_responses(self, job_id: str, status: RunningStatus = RunningStatus.SUCCESS) -> None: responses.add(responses.Response( method='GET', url=self._rpc_handler.url + _ENDPOINT_JOB_RESULT + job_id, status=200, json=self.get_job_result_response_body_from_job_status(status))) def _set_get_platform_details_responses(self) -> None: responses.add(responses.Response( method='GET', url=self._rpc_handler.url + _ENDPOINT_PLATFORM_DETAILS + quote_plus(self._rpc_handler.name), status=200, json=self._platform_info)) def _set_get_new_platform_details_responses(self, status: int) -> None: responses.add(responses.Response( method='GET', url=self._rpc_handler.url + _ENDPOINT_PLATFORM_DETAILS_NEW + quote_plus(self._rpc_handler.name) + _PROCESSOR_SECTION, status=status, json=self._platform_info)) def _set_job_availability_responses(self) -> None: responses.add(responses.Response( method='GET', url=self._rpc_handler.url + _ENDPOINT_JOB_AVAILABILITY[:-1], status=200, json=self._job_availability_response)) def _reset_job_availability_responses(self) -> None: responses.remove(responses.Response( method='GET', url=self._rpc_handler.url + _ENDPOINT_JOB_AVAILABILITY[:-1])) def get_rpc_handler_for_tests(name: str = "sim:test", url: str = "https://test", token: str = "test_token") -> RPCHandler: """Return a mocked rpc_handler :param platform_name: RPCHandler name, defaults to "sim:test" :param url: RPCHandler url, defaults to "https://test" :param token: RPCHandler token, defaults to "test_token" :return: the mocked RPCHandler """ rpc_handler = RPCHandler(name, url, token) RPCHandlerResponsesBuilder(rpc_handler) return rpc_handler ================================================ FILE: tests/runtime/test_job.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import time from unittest.mock import patch import perceval as pcvl from perceval.runtime.job_status import RunningStatus from perceval.algorithm import Sampler from .._test_utils import LogChecker PERIOD = 0.1 def quadratic_count_down(n, period=PERIOD, progress_callback=None): l = [] for i in range(n): time.sleep(period) if progress_callback: progress_callback(i / n, "counting %d" % i) l.append(i ** 2) assert period >= 0.1 # Dummy failure condition return l @pytest.mark.long_test def test_run_sync_0(): assert (pcvl.LocalJob(quadratic_count_down, command_param_names=['n', 'period'])(5) == [0, 1, 4, 9, 16]) @pytest.mark.long_test @patch.object(pcvl.utils.logging.ExqaliburLogger, "warn") def test_run_sync_1(mock_warn): job = pcvl.LocalJob(quadratic_count_down, command_param_names=['n', 'period']) n = 5 assert job.execute_sync(n) == [0, 1, 4, 9, 16] assert job.is_complete assert job.status.success assert job.get_results() == [0, 1, 4, 9, 16] # Each iteration sleeps for assert job.status.running_time > PERIOD * n assert job.status.status == RunningStatus.SUCCESS job.status.status = RunningStatus.UNKNOWN with LogChecker(mock_warn): assert job.get_results() == [0, 1, 4, 9, 16] @pytest.mark.long_test def test_run_async(): job = pcvl.LocalJob(quadratic_count_down, command_param_names=['n', 'period']) n = 5 new_period = 0.3 assert job.execute_async(n, new_period) is job assert not job.is_complete counter = 0 while not job.is_complete: counter += 1 time.sleep(0.5) assert counter > 1 assert job.status.success assert job.status.stop_message is None assert job.get_results() == [0, 1, 4, 9, 16] assert job.status.progress == 1 # should be at least 1.5s assert job.status.running_time > new_period * n assert job.status.status == RunningStatus.SUCCESS @pytest.mark.long_test @patch.object(pcvl.utils.logging.ExqaliburLogger, "warn") def test_run_async_fail(mock_warn): job = pcvl.LocalJob(quadratic_count_down, command_param_names=['n', 'period']) assert job.execute_async(5, 0.01) is job with LogChecker(mock_warn) as warn_log_checker: while not job.is_complete: time.sleep(1) assert not job.status.success assert job.status.progress == pytest.approx(0.8) assert job.status.status == RunningStatus.ERROR assert "AssertionError" in job.status.stop_message assert job.status.running_time < 0.5 job.status.status = RunningStatus.UNKNOWN with warn_log_checker: assert job.get_results() == None @pytest.mark.long_test def test_run_async_cancel(): job = pcvl.LocalJob(quadratic_count_down, command_param_names=['n', 'period']) assert job.execute_async(5, 0.3) is job job.cancel() while job.is_running: time.sleep(0.1) assert job.status.status == RunningStatus.CANCELED def test_get_res_run_async(): u = pcvl.Unitary(pcvl.Matrix.random_unitary(6)) # a random unitary matrix bs = pcvl.BasicState("|1,0,1,0,1,0>") # basic state proc = pcvl.Processor("SLOS", u) # a processor with a circuit formed of random unitary matrix proc.with_input(bs) # setting up the input to the processor job = Sampler(proc).sample_count # create a sampler job job.execute_async(10000) while not job.is_complete: time.sleep(0.01) res_1st_call = job.get_results() res_2nd_call = job.get_results() assert isinstance(res_1st_call["results"], pcvl.BSCount) assert isinstance(res_2nd_call["results"], pcvl.BSCount) assert res_1st_call["results"] == res_2nd_call["results"] assert res_1st_call["global_perf"] == res_2nd_call["global_perf"] ================================================ FILE: tests/runtime/test_job_group.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import json import pytest import requests from unittest.mock import MagicMock, patch import responses from perceval.runtime import JobGroup, RemoteJob, RunningStatus from perceval.runtime.rpc_handler import RPCHandler from perceval.components import catalog from perceval.algorithm import Sampler from perceval.utils import BasicState from tests.runtime._mock_rpc_handler import RPCHandlerResponsesBuilder, CloudEndpoint TEST_JG_NAME = 'UnitTest_Job_Group' TOKEN = "test_token" PLATFORM_NAME = "sim:test" URL = "https://test" RPC_HANDLER = RPCHandler(PLATFORM_NAME, URL, TOKEN) @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_init(mock_write_file): jg = JobGroup(TEST_JG_NAME) assert jg.name == TEST_JG_NAME assert len(jg) == 0 # empty job group assert mock_write_file.call_count == 1 @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_load(mock_write_file: MagicMock): jg = JobGroup(TEST_JG_NAME) remote_job_dict = { 'id': None, 'name': "my_job", 'status': None, 'job_context': None, 'request_data': {'payload': {}}, 'delta_parameters': {'command': {}, 'mapping': {}}, 'metadata': { 'headers': RPC_HANDLER.headers, 'platform': RPC_HANDLER.name, 'url': RPC_HANDLER.url, 'proxies': RPC_HANDLER.proxies} } jg_dict = { 'created_date': '20250219_103020', 'modified_date': '20250219_103020', 'job_group_data': [remote_job_dict, remote_job_dict] } jg._from_json(jg_dict) jg._write_to_file() last_saved_jg_dict = json.loads(mock_write_file.call_args_list[-1][0][1]) for key, value in last_saved_jg_dict.items(): if key == 'modified_date': assert jg_dict[key] < value elif key == 'request_data': assert jg_dict['body'] == value, f"Failed for key {key}: {jg_dict['body']} != {value}" else: assert jg_dict[key] == value, f"Failed for key {key}: {jg_dict[key]} != {value}" @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_add(mock_write_file): RPCHandlerResponsesBuilder(RPC_HANDLER) job_name = "remote_job_" jg = JobGroup(TEST_JG_NAME) expected_write_call_count = 1 assert mock_write_file.call_count == expected_write_call_count for i in range(10): jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, job_name + str(i))) expected_write_call_count += 1 assert mock_write_file.call_count == expected_write_call_count assert len(jg) == 10 remote_job_dict = { 'id': None, 'name': job_name, 'status': None, 'job_context': None, 'request_data': {'payload': {}}, 'delta_parameters': {'command': {}, 'mapping': {}}, 'metadata': { 'headers': RPC_HANDLER.headers, 'platform': RPC_HANDLER.name, 'url': RPC_HANDLER.url, 'proxies': RPC_HANDLER.proxies} } for i, job_info in enumerate(jg._to_json()['job_group_data']): remote_job_dict['name'] = job_name + str(i) assert job_info == remote_job_dict assert mock_write_file.call_count == expected_write_call_count @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_add_errors(mock_write_file): # creating a local job - sampling p = catalog["postprocessed cnot"].build_processor() p.with_input(BasicState([0, 1, 0, 1])) sampler = Sampler(p) jg = JobGroup(TEST_JG_NAME) assert mock_write_file.call_count == 1 with pytest.raises(TypeError): jg.add(sampler.sample_count) assert mock_write_file.call_count == 1 @pytest.mark.long_test @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_classic_run(mock_write_file): rpc_handler_responses_builder = RPCHandlerResponsesBuilder(RPC_HANDLER) rj_nmb = 2 jg = JobGroup(TEST_JG_NAME) expected_write_call_count = 1 assert mock_write_file.call_count == expected_write_call_count for _ in range(rj_nmb): jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, 'my_remote_job')) expected_write_call_count += 1 assert mock_write_file.call_count == expected_write_call_count assert len(jg) == rj_nmb assert len(responses.calls) == 0 group_progress = jg.progress() # no calls or write since jobs have not been sent assert len(responses.calls) == 0 assert mock_write_file.call_count == expected_write_call_count assert group_progress == {'Total': rj_nmb, 'Finished': [0, {'successful': 0, 'unsuccessful': 0}], 'Unfinished': [rj_nmb, {'sent': 0, 'not sent': rj_nmb}]} # Running jobs rpc_handler_responses_builder.set_job_availability_count(2) jg.run_parallel() expected_write_call_count += 2 * rj_nmb assert len(responses.calls) == 2 * rj_nmb + 1 # 1 for token availability call assert mock_write_file.call_count == expected_write_call_count assert rpc_handler_responses_builder.last_payload.get("job_group_name") == TEST_JG_NAME group_progress = jg.progress() assert len(responses.calls) == 2 * rj_nmb + 1 assert all([CloudEndpoint.from_response(call.response) == CloudEndpoint.JobStatus for call in responses.calls[rj_nmb + 1:]]) assert mock_write_file.call_count == expected_write_call_count assert group_progress == {'Total': rj_nmb, 'Finished': [rj_nmb, {'successful': rj_nmb, 'unsuccessful': 0}], 'Unfinished': [0, {'sent': 0, 'not sent': 0}]} for _ in range(rj_nmb): jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, 'my_remote_job')) expected_write_call_count += 1 assert mock_write_file.call_count == expected_write_call_count group_progress = jg.progress() assert len(responses.calls) == rj_nmb * 2 + 1 assert mock_write_file.call_count == expected_write_call_count current_group_progress = {'Total': rj_nmb*2, 'Finished': [rj_nmb, {'successful': rj_nmb, 'unsuccessful': 0}], 'Unfinished': [rj_nmb, {'sent': 0, 'not sent': rj_nmb}]} assert group_progress == current_group_progress assert mock_write_file.call_count == expected_write_call_count # Test complex load new_jg = JobGroup(TEST_JG_NAME) expected_write_call_count += 1 assert mock_write_file.call_count == expected_write_call_count new_jg._from_json(jg._to_json()) # No call on load assert len(responses.calls) == rj_nmb * 2 + 1 assert mock_write_file.call_count == expected_write_call_count group_progress = jg.progress() assert group_progress == current_group_progress # No call on load assert len(responses.calls) == rj_nmb * 2 + 1 assert mock_write_file.call_count == expected_write_call_count @pytest.mark.long_test @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_save_on_error(mock_write_file): rpc_handler_responses_builder = RPCHandlerResponsesBuilder(RPC_HANDLER) rpc_handler_responses_builder.set_job_status_sequence([RunningStatus.SUCCESS, None]) for method_idx in range(2): jg = JobGroup(TEST_JG_NAME) for _ in range(2): jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, 'my_remote_job')) with pytest.raises(requests.exceptions.HTTPError): if method_idx == 0: jg.run_parallel() else: jg.run_sequential(0.1) last_saved_jg_dict = json.loads(mock_write_file.call_args_list[-1][0][1]) jg = JobGroup(TEST_JG_NAME) jg._from_json(last_saved_jg_dict) remote_jobs = jg.remote_jobs assert sum(1 for job in remote_jobs if job.was_sent) == 1 assert sum(1 for job in remote_jobs if not job.was_sent) == 1 assert sum(1 for job in remote_jobs if job.is_success) == 1 @pytest.mark.long_test @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_run_advance(mock_write_file): rpc_handler_responses_builder = RPCHandlerResponsesBuilder(RPC_HANDLER) rpc_handler_responses_builder.set_job_status_sequence( [RunningStatus.SUCCESS, RunningStatus.ERROR, RunningStatus.ERROR]) jg = JobGroup(TEST_JG_NAME) for _ in range(3): jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, 'my_remote_job')) jg.run_parallel() assert rpc_handler_responses_builder.last_payload.get("job_group_name") == TEST_JG_NAME jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, 'my_remote_job')) rpc_handler_responses_builder.set_job_status_sequence([]) rpc_handler_responses_builder.set_default_job_status(RunningStatus.SUCCESS) assert jg.progress() == {'Total': 4, 'Finished': [3, {'successful': 1, 'unsuccessful': 2}], 'Unfinished': [1, {'sent': 0, 'not sent': 1}]} jg.run_parallel() assert jg.progress() == {'Total': 4, 'Finished': [4, {'successful': 2, 'unsuccessful': 2}], 'Unfinished': [0, {'sent': 0, 'not sent': 0}]} @pytest.mark.long_test @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_rerun(mock_write_file): rpc_handler_responses_builder = RPCHandlerResponsesBuilder(RPC_HANDLER) rpc_handler_responses_builder.set_job_status_sequence( [RunningStatus.SUCCESS, RunningStatus.ERROR, RunningStatus.ERROR]) jg = JobGroup(TEST_JG_NAME) for _ in range(3): jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, 'my_remote_job')) jg.run_parallel() assert rpc_handler_responses_builder.last_payload.get("job_group_name") == TEST_JG_NAME jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, 'my_remote_job')) rpc_handler_responses_builder.set_job_status_sequence([]) rpc_handler_responses_builder.set_default_job_status(RunningStatus.SUCCESS) assert jg.progress() == {'Total': 4, 'Finished': [3, {'successful': 1, 'unsuccessful': 2}], 'Unfinished': [1, {'sent': 0, 'not sent': 1}]} jg.rerun_failed_parallel(replace_failed_jobs=False) assert jg.progress() == {'Total': 6, 'Finished': [5, {'successful': 3, 'unsuccessful': 2}], 'Unfinished': [1, {'sent': 0, 'not sent': 1}]} jg.rerun_failed_parallel(replace_failed_jobs=True) assert jg.progress() == {'Total': 6, 'Finished': [5, {'successful': 5, 'unsuccessful': 0}], 'Unfinished': [1, {'sent': 0, 'not sent': 1}]} @pytest.mark.long_test @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_launch_async(mock_write_file): rpc_handler_responses_builder = RPCHandlerResponsesBuilder(RPC_HANDLER) rpc_handler_responses_builder.set_job_status_sequence( [RunningStatus.WAITING, RunningStatus.SUCCESS, RunningStatus.ERROR]) jg = JobGroup(TEST_JG_NAME) for _ in range(13): jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, 'my_remote_job')) rpc_handler_responses_builder.set_job_availability_count(3) jg.launch_async_jobs() assert rpc_handler_responses_builder.last_payload.get("job_group_name") == TEST_JG_NAME assert jg.progress() == {'Total': 13, 'Finished': [2, {'successful': 1, 'unsuccessful': 1}], 'Unfinished': [11, {'sent': 1, 'not sent': 10}]} rpc_handler_responses_builder.set_job_availability_count(100) rpc_handler_responses_builder.set_job_status_sequence([]) rpc_handler_responses_builder.set_default_job_status(RunningStatus.ERROR) jg.launch_async_jobs() assert jg.progress() == {'Total': 13, 'Finished': [12, {'successful': 1, 'unsuccessful': 11}], 'Unfinished': [1, {'sent': 1, 'not sent': 0}]} rpc_handler_responses_builder.set_default_job_status(RunningStatus.SUCCESS) jg.relaunch_async_failed_jobs(concurrent_job_count=3, replace_failed_jobs=False) assert jg.progress() == {'Total': 16, 'Finished': [15, {'successful': 4, 'unsuccessful': 11}], 'Unfinished': [1, {'sent': 1, 'not sent': 0}]} jg.relaunch_async_failed_jobs(replace_failed_jobs=True) assert jg.progress() == {'Total': 16, 'Finished': [15, {'successful': 15, 'unsuccessful': 0}], 'Unfinished': [1, {'sent': 1, 'not sent': 0}]} @pytest.mark.long_test @patch.object(JobGroup._PERSISTENT_DATA, 'write_file') def test_cancel_all(mock_write_file): rpc_handler_responses_builder = RPCHandlerResponsesBuilder(RPC_HANDLER) rpc_handler_responses_builder.set_default_job_status(RunningStatus.WAITING) rpc_handler_responses_builder.set_job_availability_count(8) jg = JobGroup(TEST_JG_NAME) for _ in range(13): jg.add(RemoteJob({'payload': {}}, RPC_HANDLER, 'my_remote_job')) jg.launch_async_jobs() # Unfinished jobs are required in order to cancel_all() doing something assert jg.progress() == {'Total': 13, 'Finished': [0, {'successful': 0, 'unsuccessful': 0}], 'Unfinished': [13, {'sent': 8, 'not sent': 5}]} jg.cancel_all() ## We cannot check that the jobs were cancelled ## as the MockRPCHandler cannot modify the response for a given job ================================================ FILE: tests/runtime/test_payload_generation.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """module test payload generation""" import pytest from perceval import RemoteProcessor, BasicState, catalog, PayloadGenerator from perceval.serialization._constants import ZIP_PREFIX from tests.runtime._mock_rpc_handler import get_rpc_handler_for_tests COMMAND_NAME = 'my_command' def _get_remote_processor(m: int = 8): return RemoteProcessor(rpc_handler=get_rpc_handler_for_tests(), m=m) def test_payload_basics(): """test payload basics infos""" rp = _get_remote_processor() rp.min_detected_photons_filter(4) data = rp.prepare_job_payload(COMMAND_NAME) assert 'platform_name' in data and data['platform_name'] == rp._rpc_handler.name assert 'pcvl_version' in data assert 'process_id' in data assert 'payload' in data payload = data['payload'] assert 'command' in payload and payload['command'] == COMMAND_NAME assert 'experiment' in payload and payload['experiment'].startswith(ZIP_PREFIX) # Circuits are compressed in payloads def test_payload_parameters(): """test parameters of payload""" n_params = 5 rp = _get_remote_processor() params = {f'param{i}': f'value{i}' for i in range(n_params)} rp.set_parameters(params) rp.min_detected_photons_filter(0) payload = rp.prepare_job_payload(COMMAND_NAME)['payload'] assert 'parameters' in payload for i in range(n_params): assert f'param{i}' in payload['parameters'] assert payload['parameters'][f'param{i}'] == f'value{i}' def test_payload_cnot(): """test payload with cnot""" rp = _get_remote_processor() heralded_cnot = catalog['heralded cnot'].build_processor() pp_cnot = catalog['postprocessed cnot'].build_processor() rp.add(0, heralded_cnot) rp.add(0, pp_cnot) rp.add(4, pp_cnot) assert rp.m == 8 assert rp.circuit_size == 14 # 8 modes of interest + 6 ancillaries input_state = BasicState([1, 0] * 4) rp.with_input(input_state) with pytest.raises(ValueError): rp.prepare_job_payload(COMMAND_NAME) # Missing min_detected_photons rp.min_detected_photons_filter(4) data = rp.prepare_job_payload(COMMAND_NAME) assert 'platform_name' in data and data['platform_name'] == rp._rpc_handler.name assert 'pcvl_version' in data assert 'process_id' in data assert 'payload' in data payload = data['payload'] assert 'command' in payload and payload['command'] == COMMAND_NAME assert 'experiment' in payload and payload['experiment'].startswith( ZIP_PREFIX) # Circuits are compressed in payloads def test_payload_generator(): data = PayloadGenerator.generate_payload(COMMAND_NAME) assert 'pcvl_version' in data assert 'process_id' in data assert 'payload' in data payload = data['payload'] assert 'command' in payload and payload['command'] == COMMAND_NAME assert 'experiment' in payload and payload['experiment'].startswith(ZIP_PREFIX) # Circuits are compressed in payloads ================================================ FILE: tests/runtime/test_remote_config.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import pytest import platform from perceval.utils.persistent_data import _CONFIG_FILE_NAME from perceval.runtime.remote_config import RemoteConfig, QUANDELA_CLOUD_URL MISSING_KEY = "MISSING_ENV_VAR" ENV_VAR_KEY = "DUMMY_ENV_VAR" TOKEN_FROM_ENV = "DUMMY_TOKEN_FROM_ENV" TOKEN_FROM_CACHE = "DUMMY_TOKEN_FROM_CACHE" TOKEN_FROM_FILE = "DUMMY_TOKEN_FROM_FILE" PROXY_FROM_CACHE = {'https': 'socks5h://USER:PWD@DUMMY_PROXY_FROM_CACHE:1080/'} PROXY_FROM_FILE = {'https': 'socks5h://USER:PWD@DUMMY_PROXY_FROM_FILE:1080/'} URL_FROM_CACHE = "DUMMY_URL_FROM_CACHE" URL_FROM_FILE = "DUMMY_URL_FROM_FILE" def test_remote_config_env_var_vs_cache(tmp_path, monkeypatch): os.environ[ENV_VAR_KEY] = TOKEN_FROM_ENV # Write a temporary environment variable monkeypatch.setenv('PCVL_PERSISTENT_PATH', str(tmp_path)) remote_config = RemoteConfig() assert remote_config._get_token_from_env_var() is None assert remote_config._token is None assert remote_config._proxies is None assert remote_config._url is None remote_config.set_token_env_var(ENV_VAR_KEY) assert remote_config.get_token() == TOKEN_FROM_ENV assert remote_config._token == TOKEN_FROM_ENV remote_config.set_token(TOKEN_FROM_CACHE) assert remote_config.get_token() == TOKEN_FROM_CACHE remote_config.set_proxies(PROXY_FROM_CACHE) assert remote_config.get_proxies() == PROXY_FROM_CACHE remote_config.set_url(URL_FROM_CACHE) assert remote_config.get_url() == URL_FROM_CACHE remote_config.clear_cache() assert remote_config._token is None assert remote_config.get_token() == TOKEN_FROM_ENV assert remote_config._proxies is None assert remote_config.get_proxies() == {} assert remote_config._url is None assert remote_config.get_url() == QUANDELA_CLOUD_URL del os.environ[ENV_VAR_KEY] # Remove the environment variable remote_config.clear_cache() assert remote_config._get_token_from_env_var() is None def test_remote_config_from_file(tmp_path, monkeypatch): monkeypatch.setenv('PCVL_PERSISTENT_PATH', str(tmp_path)) remote_config = RemoteConfig() persistent_data = remote_config._persistent_data if persistent_data.load_config(): pytest.skip("Skipping this test because of an existing user config") persistent_data.clear_all_data() assert remote_config.get_token() == '' assert remote_config.get_proxies() == {} assert remote_config.get_url() == QUANDELA_CLOUD_URL remote_config.set_token(TOKEN_FROM_FILE) remote_config.set_proxies(PROXY_FROM_FILE) remote_config.set_url(URL_FROM_FILE) remote_config.save() assert remote_config.get_token() == TOKEN_FROM_FILE assert remote_config.get_proxies() == PROXY_FROM_FILE assert remote_config.get_url() == URL_FROM_FILE remote_config.clear_cache() persistent_data.clear_all_data() assert remote_config.get_token() == '' assert remote_config.get_proxies() == {} assert remote_config.get_url() == QUANDELA_CLOUD_URL remote_config.set_token(TOKEN_FROM_FILE) remote_config.set_proxies(PROXY_FROM_FILE) remote_config.set_url(URL_FROM_FILE) remote_config.save() assert remote_config.get_token() == TOKEN_FROM_FILE assert remote_config.get_proxies() == PROXY_FROM_FILE assert remote_config.get_url() == URL_FROM_FILE remote_config.set_token(TOKEN_FROM_FILE) remote_config.set_proxies(PROXY_FROM_FILE) remote_config.set_url(URL_FROM_FILE) remote_config.save() assert remote_config.get_token() == TOKEN_FROM_FILE assert remote_config.get_proxies() == PROXY_FROM_FILE assert remote_config.get_url() == URL_FROM_FILE remote_config.clear_cache() persistent_data.clear_all_data() assert remote_config.get_token() == '' assert remote_config.get_proxies() == {} assert remote_config.get_url() == QUANDELA_CLOUD_URL @pytest.mark.skipif(platform.system() == "Windows", reason="chmod doesn't works on windows") def test_config_file_access(tmp_path, monkeypatch): monkeypatch.setenv('PCVL_PERSISTENT_PATH', str(tmp_path)) remote_config = RemoteConfig() persistent_data = remote_config._persistent_data if persistent_data.load_config(): pytest.skip("Skipping this test because of an existing user config") directory = persistent_data.directory os.chmod(directory, 0o000) with pytest.warns(UserWarning): # warning because config file cannot be saved remote_config.set_token(TOKEN_FROM_FILE) remote_config.set_proxies(PROXY_FROM_FILE) remote_config.set_url(URL_FROM_FILE) remote_config.save() os.chmod(directory, 0o777) token_file = os.path.join(directory, _CONFIG_FILE_NAME) remote_config.set_token(TOKEN_FROM_FILE) remote_config.set_proxies(PROXY_FROM_FILE) remote_config.set_url(URL_FROM_FILE) remote_config.save() remote_config.clear_cache() assert remote_config.get_token() == TOKEN_FROM_FILE assert remote_config.get_proxies() == PROXY_FROM_FILE assert remote_config.get_url() == URL_FROM_FILE os.chmod(token_file, 0o000) with pytest.warns(UserWarning): # warning because config file cannot be retrieved temp_remote_config = RemoteConfig() temp_remote_config._persistent_data = persistent_data temp_remote_config.clear_cache() assert temp_remote_config.get_token() == '' assert temp_remote_config.get_proxies() == {} assert temp_remote_config.get_url() == QUANDELA_CLOUD_URL os.chmod(token_file, 0o777) persistent_data.clear_all_data() ================================================ FILE: tests/runtime/test_remote_job.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """module test remote job""" import pytest from unittest.mock import patch from time import sleep import perceval as pcvl from perceval import Detector from perceval.algorithm import Sampler from perceval.runtime import RemoteJob, RunningStatus from perceval.runtime.rpc_handler import RPCHandler from perceval.utils.dist_metrics import tvd_dist from perceval.utils.conversion import sample_count_to_probs from perceval.utils.logging import channel from .._test_utils import LogChecker from tests.runtime._mock_rpc_handler import RPCHandlerResponsesBuilder, get_rpc_handler_for_tests, _TIMESTAMP SIMPLE_PAYLOAD = {"command": "probs", "circuit": ":PCVL:zip:eJyzCnAO87FydM4sSi7NLLFydfTM9K9wdI7MSg52DsyO9AkNCtWu9DANqMj3cg50hAPP9GwvBM+xEKgWwXPxRFNrEegYlu/jDNTj7mzoGhZQnGEWYkF1ewCY7jxM", "input_state": ":PCVL:BasicState:|1,1>", "parameters": {"min_detected_photons": 2}, "max_shots": 10000, "job_context": None} @patch.object(pcvl.utils.logging.ExqaliburLogger, "warn") def test_remote_job(mock_warn): rpc_handler = RPCHandler("sim:test", "https://test", "test_token") # RemoteJob init job_name = 'my_job' rj = RemoteJob({"payload": SIMPLE_PAYLOAD}, rpc_handler, job_name) assert rj.name == job_name for job_name in [None, 42]: with pytest.raises(TypeError): rj = RemoteJob({"payload": SIMPLE_PAYLOAD}, rpc_handler, job_name) rpc_handler_responses_builder = RPCHandlerResponsesBuilder(rpc_handler) # SUCCESS rpc_handler_responses_builder.set_default_job_status(RunningStatus.SUCCESS) rj = RemoteJob({"payload": SIMPLE_PAYLOAD}, rpc_handler, 'my_job') rj.execute_async() assert rj.is_complete with pytest.raises(RuntimeError): rj.rerun() rj.get_results() # no throw # UNKNOWN rpc_handler_responses_builder.set_default_job_status(RunningStatus.UNKNOWN) rj = RemoteJob({"payload": SIMPLE_PAYLOAD}, rpc_handler, 'my_job') rj.execute_async() assert rj.status.unknown with LogChecker(mock_warn): with pytest.raises(RuntimeError): rj.get_results() # CANCELED rpc_handler_responses_builder.set_default_job_status(RunningStatus.CANCELED) rj = RemoteJob({"payload": SIMPLE_PAYLOAD}, rpc_handler, 'my_job') rj.execute_async() assert rj.status.canceled assert rj.status.stop_message == 'Cancel requested from web interface' new_rj = rj.rerun() assert new_rj.id != rj.id @patch.object(pcvl.utils.logging.ExqaliburLogger, "error") def test_get_status_response_resilience(mock_warn): rpc_handler = RPCHandler("sim:test", "https://test", "test_token") rpc_handler_responses_builder = RPCHandlerResponsesBuilder(rpc_handler) # missing one field response_body = { # 'duration' is missing 'progress': 20, 'status': RunningStatus.to_server_response(RunningStatus.SUCCESS), 'creation_datetime': _TIMESTAMP, 'start_time': 1, 'status_message': '' } rpc_handler_responses_builder.set_job_status_custom_responses(response_body) rj = RemoteJob({"payload": SIMPLE_PAYLOAD}, rpc_handler, 'my_job') with LogChecker(mock_warn, channel.general, 1): rj.execute_async() assert rj.is_complete rj.get_results() # no throw # wrong type for field response_body = { 'duration': 10, 'progress': 20, 'status': RunningStatus.to_server_response(RunningStatus.SUCCESS), 'creation_datetime': _TIMESTAMP, 'start_time': None, # 'start_time' is not float 'status_message': '' } rpc_handler_responses_builder.set_job_status_custom_responses(response_body) rj = RemoteJob({"payload": SIMPLE_PAYLOAD}, rpc_handler, 'my_job') with LogChecker(mock_warn, channel.general, 1): rj.execute_async() assert rj.is_complete rj.get_results() # no throw @pytest.mark.parametrize('catalog_item', ["klm cnot", "heralded cnot", "postprocessed cnot", "heralded cz"]) def test_mock_remote_with_gates(catalog_item): """test mock remote with gates""" noise = pcvl.NoiseModel( g2=0.003, transmittance=0.06, phase_imprecision=0, indistinguishability=0.92) p = pcvl.catalog[catalog_item].build_processor() p.noise = noise rp = pcvl.RemoteProcessor.from_local_processor( p, rpc_handler=get_rpc_handler_for_tests() ) assert p.heralds == rp.heralds assert p.post_select_fn == rp.post_select_fn assert p.noise == rp.noise assert noise == rp.noise for input_state in [pcvl.BasicState(state) for state in [[0, 1, 0, 1], [0, 1, 1, 0], [1, 0, 0, 1], [1, 0, 1, 0]]]: p.with_input(input_state) rp.with_input(input_state) assert p.input_state == rp.input_state @pytest.mark.skip(reason="need a token and a worker available") @pytest.mark.parametrize('catalog_item', ["klm cnot", "heralded cnot", "postprocessed cnot", "heralded cz"]) def test_remote_with_gates_probs(catalog_item): noise = pcvl.NoiseModel( g2=0.003, transmittance=0.06, phase_imprecision=0, indistinguishability=0.92) p = pcvl.catalog[catalog_item].build_processor() p.min_detected_photons_filter(2 + list(p.heralds.values()).count(1)) p.noise = noise rp = pcvl.RemoteProcessor.from_local_processor(p, 'sim:altair', url='https://api.cloud.quandela.com') # platform parameters for m in range(p.circuit_size): p.add(m, Detector.threshold()) max_shots_per_call = 1E7 assert p.heralds == rp.heralds assert p.post_select_fn == rp.post_select_fn assert p._noise == rp._noise assert noise == rp._noise for input_state in [pcvl.BasicState(state) for state in [[0, 1, 0, 1], [0, 1, 1, 0], [1, 0, 0, 1], [1, 0, 1, 0]]]: rp.with_input(input_state) rs = Sampler(rp, max_shots_per_call=max_shots_per_call) job = rs.probs.execute_async() p.with_input(input_state) s = Sampler(p, max_shots_per_call=max_shots_per_call) probs = s.probs() delay = 0 while True: if job.is_complete: break assert not job.is_failed if delay == 20: assert False, "timeout for job" delay += 1 sleep(1) tvd = tvd_dist(probs['results'], job.get_results()['results']) assert tvd == pytest.approx(0.0, abs=0.2) # total variation between two distributions is less than 0.2 @pytest.mark.skip(reason="need a token and a worker available") @pytest.mark.parametrize('catalog_item', ["klm cnot", "heralded cnot", "postprocessed cnot", "heralded cz"]) def test_remote_with_gates_samples(catalog_item): noise = pcvl.NoiseModel( g2=0.003, transmittance=0.06, phase_imprecision=0, indistinguishability=0.92) p = pcvl.catalog[catalog_item].build_processor() p.min_detected_photons_filter(2 + list(p.heralds.values()).count(1)) p.noise = noise rp = pcvl.RemoteProcessor.from_local_processor( p, "sim:altair", url='https://api.cloud.quandela.com') # platform parameters for m in range(p.circuit_size): p.add(m, Detector.threshold()) max_shots_per_call = 1E7 nsamples = 1000 assert p.heralds == rp.heralds assert p.post_select_fn == rp.post_select_fn assert p._noise == rp._noise assert noise == rp._noise for input_state in [pcvl.BasicState(state) for state in [[0, 1, 0, 1], [0, 1, 1, 0], [1, 0, 0, 1], [1, 0, 1, 0]]]: rp.with_input(input_state) rs = Sampler(rp, max_shots_per_call=max_shots_per_call) job = rs.sample_count.execute_async(nsamples) p.with_input(input_state) s = Sampler(p, max_shots_per_call=max_shots_per_call) samples = s.sample_count(nsamples) delay = 0 while True: if job.is_complete: break assert not job.is_failed if delay == 20: assert False, "timeout for job" delay += 1 sleep(1) local_sim_bsd = sample_count_to_probs(samples['results']) remote_sim_bsd = sample_count_to_probs(job.get_results()['results']) tvd = tvd_dist(local_sim_bsd, remote_sim_bsd) assert tvd == pytest.approx(0.0, abs=0.2) # total variation between two distributions is less than 0.2 ================================================ FILE: tests/runtime/test_rpc_handler.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import uuid import json import pytest import requests import responses from tests.runtime._mock_rpc_handler import RPCHandlerResponsesBuilder, DEFAULT_PLATFORM_INFO, ARCHITECTURE_PLATFORM_INFO from perceval.runtime.rpc_handler import ( RPCHandler, _ENDPOINT_JOB_CANCEL, _ENDPOINT_JOB_CREATE, _ENDPOINT_JOB_RESULT, _ENDPOINT_JOB_STATUS, _ENDPOINT_JOB_RERUN ) from perceval.runtime.job_status import RunningStatus TOKEN = "test_token" PLATFORM_NAME = "sim:test" URL = "https://test" JOB_PAYLOAD: dict[str, str] = {"key": "value"} def test_create_job(): rpc_handler = RPCHandler(PLATFORM_NAME, URL, TOKEN) RPCHandlerResponsesBuilder(rpc_handler) job_id = rpc_handler.create_job(JOB_PAYLOAD) assert str(uuid.UUID(job_id, version=4)) == job_id assert len(responses.calls) == 1 create_job_request = responses.calls[0].request assert create_job_request.url == URL + _ENDPOINT_JOB_CREATE assert create_job_request.method == "POST" assert json.loads(create_job_request.body) == JOB_PAYLOAD assert create_job_request.headers['Authorization'] == f'Bearer {TOKEN}' RPCHandlerResponsesBuilder(rpc_handler, default_job_status=None) with pytest.raises(requests.exceptions.HTTPError): rpc_handler.create_job(JOB_PAYLOAD) def test_get_job_infos(): rpc_handler = RPCHandler(PLATFORM_NAME, URL, TOKEN) rpc_handler_responses = RPCHandlerResponsesBuilder(rpc_handler, default_job_status=RunningStatus.SUCCESS) job_id = rpc_handler.create_job(JOB_PAYLOAD) # Status status = rpc_handler.get_job_status(job_id) supposed_status = rpc_handler_responses.get_job_status_response_body_from_job_status(RunningStatus.SUCCESS) assert len(status) == len(supposed_status) for key in status: if isinstance(status[key], float): assert status[key] == pytest.approx(supposed_status[key]) else: status[key] == supposed_status[key] assert status['status'] == "completed" assert int(status['duration']) >= 0 assert float(status['progress']) == 1.0 assert len(responses.calls) == 2 job_status_request = responses.calls[1].request assert job_status_request.url == URL + _ENDPOINT_JOB_STATUS + job_id assert job_status_request.method == "GET" assert job_status_request.body == None assert job_status_request.headers['Authorization'] == f'Bearer {TOKEN}' # Results results = rpc_handler.get_job_results(job_id) assert results == rpc_handler_responses.get_job_result_response_body_from_job_status(RunningStatus.SUCCESS) assert results.get("results") is not None assert len(responses.calls) == 3 job_results_request = responses.calls[2].request assert job_results_request.url == URL + _ENDPOINT_JOB_RESULT + job_id assert job_results_request.method == "GET" assert job_results_request.body == None assert job_results_request.headers['Authorization'] == f'Bearer {TOKEN}' with pytest.raises(requests.exceptions.HTTPError): rpc_handler.get_job_status(str(uuid.uuid4())) with pytest.raises(requests.exceptions.HTTPError): rpc_handler.get_job_results(str(uuid.uuid4())) def test_cancel_rerun_job(): rpc_handler = RPCHandler(PLATFORM_NAME, URL, TOKEN) RPCHandlerResponsesBuilder(rpc_handler) with pytest.raises(requests.exceptions.HTTPError): rpc_handler.cancel_job(str(uuid.uuid4())) with pytest.raises(requests.exceptions.HTTPError): rpc_handler.rerun_job(str(uuid.uuid4())) # cancel for status in [RunningStatus.RUNNING, RunningStatus.WAITING]: RPCHandlerResponsesBuilder(rpc_handler, default_job_status=status) job_id = rpc_handler.create_job(JOB_PAYLOAD) rpc_handler.cancel_job(job_id) assert len(responses.calls) == 2 cancel_job_request = responses.calls[1].request assert cancel_job_request.url == URL + _ENDPOINT_JOB_CANCEL + job_id assert cancel_job_request.method == "POST" assert cancel_job_request.body == None assert cancel_job_request.headers['Authorization'] == f'Bearer {TOKEN}' for status in [RunningStatus.SUCCESS, RunningStatus.ERROR, RunningStatus.CANCELED]: RPCHandlerResponsesBuilder(rpc_handler, default_job_status=status) job_id = rpc_handler.create_job(JOB_PAYLOAD) with pytest.raises(requests.exceptions.HTTPError): rpc_handler.cancel_job(job_id) # rerun for status in [RunningStatus.ERROR, RunningStatus.CANCELED]: RPCHandlerResponsesBuilder(rpc_handler, default_job_status=status) job_id = rpc_handler.create_job(JOB_PAYLOAD) new_job_id = rpc_handler.rerun_job(job_id) assert job_id != new_job_id assert str(uuid.UUID(new_job_id, version=4)) == new_job_id assert len(responses.calls) == 2 rerun_job_request = responses.calls[1].request assert rerun_job_request.url == URL + _ENDPOINT_JOB_RERUN + job_id assert rerun_job_request.method == "POST" assert rerun_job_request.body == None assert rerun_job_request.headers['Authorization'] == f'Bearer {TOKEN}' for status in [RunningStatus.SUCCESS, RunningStatus.WAITING, RunningStatus.RUNNING]: RPCHandlerResponsesBuilder(rpc_handler, default_job_status=status) job_id = rpc_handler.create_job(JOB_PAYLOAD) with pytest.raises(requests.exceptions.HTTPError): rpc_handler.rerun_job(job_id) def test_get_platform_details(): rpc_handler = RPCHandler(PLATFORM_NAME, URL, TOKEN) platform_info = DEFAULT_PLATFORM_INFO platform_info['name'] = PLATFORM_NAME RPCHandlerResponsesBuilder(rpc_handler, platform_details=platform_info) assert platform_info == rpc_handler.fetch_platform_details() def test_get_platform_details_with_architecture(): rpc_handler = RPCHandler(PLATFORM_NAME, URL, TOKEN) platform_info = DEFAULT_PLATFORM_INFO platform_info['name'] = PLATFORM_NAME RPCHandlerResponsesBuilder(rpc_handler, platform_details=platform_info, use_new_platform_details_url=False) assert platform_info == rpc_handler.fetch_platform_details() platform_info = ARCHITECTURE_PLATFORM_INFO platform_info['name'] = PLATFORM_NAME RPCHandlerResponsesBuilder(rpc_handler, platform_details=platform_info, use_new_platform_details_url=True) assert platform_info == rpc_handler.fetch_platform_details() ================================================ FILE: tests/runtime/test_shots_estimate.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.runtime.remote_processor import RemoteProcessor, TRANSMITTANCE_KEY from perceval.components import AProcessor, Unitary, BS, PS, Experiment, Detector, FFCircuitProvider, Circuit from perceval.utils import Matrix, BasicState, P import random class _MockRemoteProcessor(RemoteProcessor): def __init__(self): AProcessor.__init__(self) # Avoid RemoteProcessor __init__ to prevent the https request from firing self._specs = {} self._perfs = { TRANSMITTANCE_KEY: 6 # in percent } self._thresholded_output = False def test_shots_estimate_trivial_filter_values(): rp = _MockRemoteProcessor() m = Matrix.random_unitary(10) rp.set_circuit(Unitary(m)) rp.with_input(BasicState([1]*5 + [0]*5)) rp.min_detected_photons_filter(1) ANY_VALUE = random.randint(1000, 9999999999) # with min_detected_photons_filter set to 1, shots and samples are the same assert rp.estimate_expected_samples(ANY_VALUE) == ANY_VALUE assert rp.estimate_required_shots(ANY_VALUE) == ANY_VALUE rp.min_detected_photons_filter(0) # same with 0 assert rp.estimate_expected_samples(ANY_VALUE) == ANY_VALUE assert rp.estimate_required_shots(ANY_VALUE) == ANY_VALUE # with a filter too high, there's no estimate rp.min_detected_photons_filter(6) assert rp.estimate_expected_samples(ANY_VALUE) == 0 assert rp.estimate_required_shots(ANY_VALUE) is None def test_shots_estimate_regular_use_case(): rp = _MockRemoteProcessor() c = BS() // PS(phi=0.2) // BS() rp.set_circuit(c) rp.with_input(BasicState([1, 1])) assert 28 < rp.estimate_expected_samples(1000) < 32 assert 32000 < rp.estimate_required_shots(1000) < 33000 def test_shots_estimate_circuit_with_variables(): rp = _MockRemoteProcessor() c = BS() // PS(phi=P("my_phase")) // BS() rp.set_circuit(c) rp.with_input(BasicState([1, 1])) assert 28 < rp.estimate_expected_samples(1000, {"my_phase": 0.2}) < 32 assert 32000 < rp.estimate_required_shots(1000, {"my_phase": 0.2}) < 33000 def test_shots_estimate_feed_forward(): exp_ff = Experiment(4) exp_ff.add(0, BS.H()) for i in range(2): exp_ff.add(i, Detector.pnr()) ffc = FFCircuitProvider(2, 0, BS.H()) ffc.add_configuration((0, 1), Circuit(2)) exp_ff.add(0, ffc) rp = _MockRemoteProcessor() rp.add(0, exp_ff) rp.with_input(BasicState([1, 0, 1, 0])) assert 28 < rp.estimate_expected_samples(1000) < 32 assert 32000 < rp.estimate_required_shots(1000) < 33000 ================================================ FILE: tests/serialization/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/serialization/test_serialization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from unittest.mock import patch import pytest import random import sympy as sp import numpy from packaging.version import Version from .._test_utils import assert_circuits_eq, assert_experiment_equals, LogChecker, assert_compiled_circuit_equals from perceval.components import ACircuit, Circuit, BSLayeredPPNR, Detector, BS, PS, TD, LC, Port, Herald, Experiment, \ catalog, FFConfigurator, FFCircuitProvider, CompiledCircuit from perceval.components.core_catalog import MZIPhaseFirst from perceval.utils import Matrix, E, P, BasicState, BSDistribution, BSCount, BSSamples, SVDistribution, StateVector, \ NoiseModel, PostSelect, Encoding from perceval.utils.logging import ExqaliburLogger from perceval.serialization import serialize, deserialize, serialize_binary, deserialize_circuit, deserialize_matrix from perceval.serialization._parameter_serialization import serialize_parameter, deserialize_parameter import perceval.components.unitary_components as comp import json def test_numeric_matrix_serialization(): input_mat = Matrix.random_unitary(10) serialized_mat = serialize(input_mat) deserialized_mat = deserialize(serialized_mat) assert (input_mat == deserialized_mat).all() input_mat = Matrix([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]]) serialized_mat = serialize(input_mat) deserialized_mat = deserialize(serialized_mat) assert (input_mat == deserialized_mat).all() def test_symbolic_matrix_serialization(): theta = P('theta') bs = comp.BS(theta=theta) input_mat = bs.U serialized_mat = serialize(input_mat) deserialized_mat = deserialize(serialized_mat) # Now, assign any value to theta: theta_value = random.random() theta.set_value(theta_value) input_mat_num = bs.compute_unitary() convert_to_numpy = sp.lambdify((), deserialized_mat.subs({'theta': theta_value}), modules=numpy) deserialized_mat_num = Matrix(convert_to_numpy()) assert numpy.allclose(input_mat_num, deserialized_mat_num) def test_symbol_serialization(): theta = P('theta') theta_deserialized = deserialize_parameter(serialize_parameter(theta)) assert theta_deserialized.is_symbolic() assert theta._symbol == theta_deserialized._symbol def _build_test_circuit(): c1 = (Circuit(3) // comp.BS(theta=1.814) // comp.PS(phi=0.215, max_error=0.33) // comp.PERM([2, 0, 1]) // (1, comp.PBS()) // comp.Unitary(Matrix.random_unitary(3))) c2 = Circuit(2) // comp.BS.H(theta=0.36, phi_tl=1.94, phi_br=5.8817, phi_bl=0.0179) // comp.PERM([1, 0]) c1.add(1, c2, merge=False).add(0, comp.HWP(xsi=0.23)).add(1, comp.QWP(xsi=0.17)).add(2, comp.WP(0.4, 0.5)) c1.add(0, comp.Barrier(2, visible=True)) c1.add(0, comp.PR(delta=0.89)) return c1 def test_circuit_serialization(): c1 = _build_test_circuit() serialized_c1 = serialize(c1) deserialized_c1 = deserialize(serialized_c1) assert_circuits_eq(c1, deserialized_c1) def test_non_unitary_serialization(): t = TD(1) t_2 = deserialize(serialize(t)) assert t is not t_2 assert float(t_2._dt) == float(t._dt) l = LC(0.7) l_2 = deserialize(serialize(l)) assert l is not l_2 assert float(l_2._loss) == float(l._loss) def test_circuit_serialization_backward_compat(): serial_circuits = { # Perceval version (key) that generated the serialized representation of a given circuit (value) "0.7": ":PCVL:ACircuit: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", #0.8 : Did not change circuit serialization #0.9 : Did not change circuit serialization #0.10 : Did not change circuit serialization #0.11 : Updated parameter serialization "0.11": ":PCVL:zip:eJxVjs0KgkAURh/IlYhgAy5uV9GZrGYqB2cZEs1PYaWhvn2ukvl2Bw6Hj3CUFQE0n/ZrBpLDaI4ToLLtGYVTVX2qg7mM+dQxFPAfvTu2ErwXd6WM+q52AmQnUe/Zq4QNDjc7ZonBIoybaNup5vCgthfwpFG+dAsMc8l7HVySq9dFv7vzP8yeC2n6A8+rQV4=", #0.12 : Did not change circuit serialization #0.13 : Did not change circuit serialization #1.0 : Added new parameter to Detector "1.0": ":PCVL:Detector:CgRQUE5SEAQYAg==" } for perceval_version, serial_c in serial_circuits.items(): try: deserialize(serial_c) except Exception as e: pytest.fail(f"Circuit serial representation generated with Perceval {perceval_version} failed: {e}") def test_port_serialization(): p = Port(Encoding.DUAL_RAIL, name = "test") p_2 = deserialize(serialize(p)) assert p is not p_2 assert p.name == p_2.name assert p.encoding == p_2.encoding h = Herald(2, name = "test") h_2 = deserialize(serialize(h)) assert h is not h_2 assert h.user_given_name == h_2.user_given_name assert h.expected == h_2.expected h = Herald(0) # Same without user-given name h_2 = deserialize(serialize(h)) assert h is not h_2 assert h.user_given_name == h_2.user_given_name assert h._name == h_2._name assert h.expected == h_2.expected def test_basicstate_serialization(): states = [ BasicState("|0,1>"), BasicState([0, 1, 0, 0, 1, 0]), BasicState("|{P:H}{P:V},0>"), BasicState("|{0},{1},{2}{0}>"), ] for s in states: serialized = serialize(s) deserialized = deserialize(serialized) assert s == deserialized def test_svdistribution_serialization(): svd = SVDistribution() svd[StateVector("|0,1>")] = 0.2 svd[BasicState("|1,0>")] = 0.3 svd[BasicState("|1,1>")] = 0.5 svd2 = deserialize(serialize(svd)) assert svd == svd2 svd_empty = SVDistribution() deserialized_svd_empty = deserialize(serialize(svd_empty)) assert deserialized_svd_empty == svd_empty def test_bsdistribution_serialization(): bsd = BSDistribution() bsd.add(BasicState([0, 1]), 0.4) bsd.add(BasicState([1, 0]), 0.4) bsd.add(BasicState([1, 1]), 0.2) deserialized_bsd = deserialize(serialize(bsd)) assert bsd == deserialized_bsd bsd_empty = BSDistribution() deserialized_bsd_empty = deserialize(serialize(bsd_empty)) assert bsd_empty == deserialized_bsd_empty def test_bscount_serialization(): bsc = BSCount() bsc.add(BasicState([0, 1]), 95811) bsc.add(BasicState([1, 0]), 56598) bsc.add(BasicState([1, 1]), 10558) deserialized_bsc = deserialize(serialize(bsc)) assert bsc == deserialized_bsc bsc_empty = BSCount() deserialized_bsc_empty = deserialize(serialize(bsc_empty)) assert deserialized_bsc_empty == bsc_empty def test_bssamples_serialization(): samples = BSSamples() for j in range(50): for i in range(11): samples.append(BasicState([0, 1, 0])) for i in range(13): samples.append(BasicState([1, 0, 0])) for i in range(17): samples.append(BasicState([0, 0, 1])) deserialized_samples = deserialize(serialize(samples)) assert deserialized_samples == samples empty_samples = BSSamples() deserialized_empty_samples = deserialize(serialize(empty_samples)) assert empty_samples == deserialized_empty_samples def test_sv_serialization(): sv = (1+1j) * StateVector("|0,1>") + (1-1j) * StateVector("|1,0>") sv_serialized = serialize(sv) assert sv_serialized == ":PCVL:StateVector:(0.5,0.5)*|0,1>+(0.5,-0.5)*|1,0>" \ or sv_serialized == ":PCVL:StateVector:(0.5,-0.5)*|1,0>+(0.5,0.5)*|0,1>" # Order does not matter sv_deserialized = deserialize(sv_serialized) assert sv == sv_deserialized def test_noise_model_serialization(): empty_nm = NoiseModel() empty_nm_ser = serialize(empty_nm) empty_nm_deser = deserialize(empty_nm_ser) assert empty_nm == empty_nm_deser nm = NoiseModel(brightness=0.1, indistinguishability=0.2, g2=0.3, g2_distinguishable=True, transmittance=0.4, phase_imprecision=0.5, phase_error=0.08) nm_ser = serialize(nm) nm_deser = deserialize(nm_ser) assert nm == nm_deser @pytest.mark.parametrize("ps", (PostSelect(), PostSelect("[0]==0 & [1,2]==1 & [3,4]==1 & [5]==0"), PostSelect("[0]>0&[1]<2&[2]==1"))) def test_postselect_serialization(ps): ps_ser = serialize(ps) ps_deser = deserialize(ps_ser) assert ps == ps_deser @pytest.mark.parametrize("detector", (BSLayeredPPNR(2, .6), Detector.pnr(), Detector.threshold(), Detector.ppnr(4, 2), Detector.ppnr(4, 2, 0.6))) def test_detector_serialization(detector): serialized = serialize(detector) deserialized = deserialize(serialized) assert detector.name == deserialized.name, "Wrong deserialized detector name" if isinstance(detector, BSLayeredPPNR): assert detector._layers == deserialized._layers and detector._r == deserialized._r, \ "Wrong deserialized detector parameters" else: assert detector._wires == deserialized._wires and detector.max_detections == deserialized.max_detections, \ "Wrong deserialized detector parameters" def test_experiment_serialization(): compiled_circuit = CompiledCircuit("chip name", 2, [], Version("1.0")) # Empty experiment e = Experiment() ser_e = serialize(e) e_2 = deserialize(ser_e) assert_experiment_equals(e, e_2) # Fully complete experiment e = Experiment(4, NoiseModel(.4, .5, .6), name="test") e.min_detected_photons_filter(2) # Includes components, heralds, postselection, ports e.add(0, catalog["postprocessed cnot"].build_processor()) e.add(0, LC(.1)) # With non-unitary component e.with_input(BasicState([1, 0, 1, 0])) e.add(0, Detector.ppnr(24, 4)) e.add(1, Detector.threshold()) mzi = catalog['mzi phase last'].build_circuit() ffc = FFConfigurator(2, 0, mzi, {"phi_a": 0, "phi_b": 0}, "ffc name") ffc.add_configuration((0, 1), {"phi_a": 3, "phi_b": 0}) ffc.add_configuration((1, 0), {"phi_a": 0, "phi_b": 3}) e.add(0, ffc) ffcp = FFCircuitProvider(1, 0, compiled_circuit // BS.H(), "ffcp name") ffcp.add_configuration((0,), compiled_circuit // BS.Rx()) subexp = Experiment(2) subexp.add(0, BS.Ry()) ffcp.add_configuration((1,), subexp) e.add(1, ffcp) e.add(2, Detector.pnr()) e.add(3, BSLayeredPPNR(2)) ser_e = serialize(e) e_2 = deserialize(ser_e) assert_experiment_equals(e, e_2) # include compiled circuit e = Experiment(3, None, "-") e.add(1, compiled_circuit) ser_e = serialize(e) e_2 = deserialize(ser_e) assert_experiment_equals(e, e_2) def test_compiled_circuit_serialization(): circuit = CompiledCircuit("chip name", MZIPhaseFirst().build_circuit(), [0., 1.], Version("1.0")) c_ser = serialize(circuit) c_deser = deserialize(c_ser) assert_compiled_circuit_equals(circuit, c_deser) def test_json(): svd = SVDistribution() svd.add(BasicState("|1,0>"), 0.5) svd.add(BasicState("|0,1>"), 0.5) encoded = serialize({"a": BasicState("|1,0>"), "b": Circuit(2) // comp.BS(), "c": Matrix.random_unitary(3), "d": svd }) s = json.dumps(encoded) d = deserialize(json.loads(s)) assert isinstance(d["a"], BasicState) assert isinstance(d["b"], ACircuit) assert isinstance(d["c"], Matrix) assert isinstance(d["d"], SVDistribution) def test_binary_serialization(): c_before = _build_test_circuit() bin_serialization = serialize_binary(c_before) assert isinstance(bin_serialization, bytes) c_after = deserialize_circuit(bin_serialization) assert_circuits_eq(c_before, c_after) with pytest.raises(TypeError): deserialize(bin_serialization) m_before = Matrix([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]]) bin_serialization = serialize_binary(m_before) assert isinstance(bin_serialization, bytes) m_after = deserialize_matrix(bin_serialization) assert numpy.allclose(m_before, m_after) def test_compress(): zip_prefix = ":PCVL:zip:" c = _build_test_circuit() assert serialize(c).startswith(zip_prefix) # Default value is to compress circuits assert serialize(c, compress=True).startswith(zip_prefix) assert serialize(c, compress=["ACircuit"]).startswith(zip_prefix) assert serialize(c, compress=["toto", "tutu", "ACircuit", "papa"]).startswith(zip_prefix) assert not serialize(c, compress=False).startswith(zip_prefix) assert not serialize(c, compress=[]).startswith(zip_prefix) assert not serialize(c, compress=["toto", "tutu", "papa"]).startswith(zip_prefix) with pytest.raises(NotImplementedError): serialize(c, compress=12).startswith(zip_prefix) d = { "input_state": BasicState([0, 1, 0, 1]), "circuit": Circuit(4) // (0, comp.BS()) // (2, comp.BS()), "integer": 43 } d_compressed = serialize(d, compress=True) assert d_compressed["input_state"].startswith(zip_prefix) assert d_compressed["circuit"].startswith(zip_prefix) assert d_compressed["integer"] == 43 # JSon-compatible integral types are neither serialized nor compressed d_not_compressed = serialize(d, compress=False) assert not d_not_compressed["input_state"].startswith(zip_prefix) assert not d_not_compressed["circuit"].startswith(zip_prefix) assert d_not_compressed["integer"] == 43 # JSon-compatible integral types are neither serialized nor compressed # Compressing a circuit serialization gives a smaller representation assert len(d_compressed["circuit"]) < len(d_not_compressed['circuit']) d_only_circuit = serialize(d, compress=["ACircuit"]) # Compress only ACircuit objects assert not d_only_circuit["input_state"].startswith(zip_prefix) assert d_only_circuit["circuit"].startswith(zip_prefix) d_only_basicstate = serialize(d, compress=["BasicState"]) # Compress only BasicState objects assert d_only_basicstate["input_state"].startswith(zip_prefix) assert not d_only_basicstate["circuit"].startswith(zip_prefix) d_default_serialize = serialize(d) # Compress only Circuit (default behaviour for Circuit and BasicState) assert not d_default_serialize["input_state"].startswith(zip_prefix) assert d_default_serialize["circuit"].startswith(zip_prefix) def test_circuit_with_expression_serialization(): p_a = P("A") p_b = P("B") sum_ab = E("A + B", {p_a, p_b}) c = Circuit(2) c.add(0, PS(phi=p_a)) c.add(0, BS(theta=sum_ab)) c.add(0, PS(phi=p_a)) c_ser = serialize(c) c_deser = deserialize(c_ser) assert "A" in c_deser.params and "B" in c_deser.params a_value = 0.2 b_value = 0.8 c_deser.param("A").set_value(a_value) c_deser.param("B").set_value(b_value) assert float(c_deser._components[1][1].param('theta')) == pytest.approx(0.2 + 0.8) p_a.set_value(a_value) p_b.set_value(b_value) assert numpy.allclose(c_deser.compute_unitary(), c.compute_unitary()) @patch.object(ExqaliburLogger, "warn") def test_deserialize_unknown_object(mock_warn): serial_unknown = ":PCVL:Unknown:byieozbfuizp" with pytest.raises(NotImplementedError): deserialize(serial_unknown) with LogChecker(mock_warn): assert deserialize(serial_unknown, strict=False) == serial_unknown # Test within a container container = [{serial_unknown: 1}] with LogChecker(mock_warn): assert deserialize(container, strict=False) == container ================================================ FILE: tests/serialization/test_serialized_containers.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import json from perceval import FockState, NoiseModel from perceval.serialization import serialize from perceval.serialization._serialized_containers import SerializedList, SerializedDict def test_basic_dict(): # No Perceval object, no nested containers d = {1: "str", "str": 6.67} d = SerializedDict(d) assert 1 in d assert d[1] == "str" assert "str" in d assert d["str"] == 6.67 assert d.get("unknown") is None # Includes perceval object fs = FockState([1, 2]) nm = NoiseModel(0.4) d = SerializedDict({0: fs}) assert d[0] == serialize(fs) d = SerializedDict({fs: 1}) assert fs in d assert d[fs] == 1 assert serialize(fs) in d # This is a side effect of storing directly the serialized versions of objects d.update({fs: 2}) assert d[fs] == 2 d["noise"] = nm assert d["noise"] == serialize(nm) # Test jsonifiable json.dumps(d) def test_basic_list(): # No Perceval object, no nested containers l = SerializedList([1, 2, "str"]) assert l[0] == 1 assert l[2] == "str" # Includes perceval object fs = FockState([1, 2]) nm = NoiseModel(0.4) l = SerializedList([1, fs, nm]) assert l[1] == serialize(fs) assert l[2] == serialize(nm) l += [fs] assert l[-1] == serialize(fs) assert l.count(fs) == 2 l2 = l + [nm] assert l2[-1] == serialize(nm) # Test jsonifiable json.dumps(l) def test_nested_containers(): fs = FockState([1, 2]) nm = NoiseModel(0.4) l = SerializedList([1, 2, {fs: 0}]) assert isinstance(l[2], SerializedDict) d = SerializedDict({fs: [nm]}) assert isinstance(d[fs], SerializedList) ================================================ FILE: tests/simulators/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/simulators/test_delay_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval import Detector, SVDistribution, post_select_distribution from perceval.simulators.delay_simulator import _retrieve_mode_count, DelaySimulator from perceval.simulators.simulator import Simulator from perceval.backends import SLOSBackend from perceval.components import Circuit, BS, TD from perceval.utils import BasicState, BSDistribution, PostSelect import pytest from .._test_utils import assert_bsd_close def test_retrieve_mode_count(): comp_list = [((0,1), None), ((1,2,3), None), ((0,1), None), ((0,1), None), ((0,), None)] assert _retrieve_mode_count(comp_list) == 4 def test_prepare_circuit(): sim = DelaySimulator(None) c = sim._prepare_circuit(BS()) # Prepare circuit shouldn't change a circuit without any TD assert isinstance(c, Circuit) assert c.m == 2 assert len(c._components) == 1 assert c._components[0][0] == (0,1) assert isinstance(c._components[0][1], BS) input_circ = [((0,1), BS()), ((0,), TD(1)), ((0,1), BS())] output_circ = sim._prepare_circuit(input_circ) assert output_circ.m == 5 def test_delay_simulation(): backend = SLOSBackend() simulator = DelaySimulator(Simulator(backend)) input_circ = [((0, 1), BS()), ((0,), TD(1)), ((0, 1), BS())] simulator.set_circuit(input_circ) res = simulator.probs(BasicState([1, 0])) expected = BSDistribution() expected[BasicState([0, 0])] = 0.25 expected[BasicState([1, 0])] = 0.25 expected[BasicState([0, 1])] = 0.25 expected[BasicState([2, 0])] = 0.125 expected[BasicState([0, 2])] = 0.125 assert_bsd_close(res, expected) simulator.set_selection(postselect=PostSelect('[1]>0')) heralded_res = simulator.probs(BasicState([1, 0])) expected = BSDistribution() expected[BasicState([0, 1])] = 0.666666666666667 expected[BasicState([0, 2])] = 0.333333333333333 assert_bsd_close(heralded_res, expected) sv = simulator.evolve(BasicState([1, 0])) assert len(sv) == 2 assert BasicState([0, 1]) in sv.keys() assert BasicState([0, 2]) in sv.keys() def test_delay_detectors_simulation(): backend = SLOSBackend() simulator = DelaySimulator(Simulator(backend)) input_circ = [((0, 1), BS()), ((0,), TD(1)), ((0, 1), BS())] simulator.set_circuit(input_circ) detectors = [Detector.threshold(), Detector.threshold()] assert simulator._prepare_detectors(detectors) == 2 * [None] + detectors + [None],\ "Wrong translation of detectors in delay simulator" res = simulator.probs_svd(SVDistribution(BasicState([1, 0])), detectors) expected = BSDistribution() expected[BasicState([0, 0])] = 0.25 expected[BasicState([1, 0])] = 0.25 + 0.125 expected[BasicState([0, 1])] = 0.25 + 0.125 assert_bsd_close(res["results"], expected) def test_invalid_delay(): backend = SLOSBackend() simulator = DelaySimulator(Simulator(backend)) input_circ = [((0, 1), BS()), ((0,), TD(0.75)), ((0, 1), BS())] with pytest.raises(ValueError): # TD parameter has to be an integer (number of periods) simulator.set_circuit(input_circ) def test_logical_selection(): backend = SLOSBackend() simulator = DelaySimulator(Simulator(backend)) input_state = BasicState([1, 0]) input_circ = [((0, 1), BS(1.54)), ((0,), TD(1)), ((0, 1), BS(1.58))] # Break symmetry simulator.set_circuit(input_circ) non_filtered = simulator.probs(input_state) heralds = {} postselect = PostSelect('[1]>0') simulator.set_selection(postselect=postselect, heralds=heralds) res = simulator.probs_svd(SVDistribution(input_state)) filtered, perf = res["results"], res["global_perf"] theo_filtered, theo_perf = post_select_distribution(non_filtered, postselect, heralds) assert_bsd_close(theo_filtered, filtered) assert theo_perf == pytest.approx(perf) heralds = {1: 0} postselect = PostSelect() simulator.set_selection(postselect=postselect, heralds=heralds) simulator.keep_heralds(False) res = simulator.probs_svd(SVDistribution(input_state)) filtered, perf = res["results"], res["global_perf"] theo_filtered, theo_perf = post_select_distribution(non_filtered, postselect, heralds, False) assert_bsd_close(theo_filtered, filtered) assert theo_perf == pytest.approx(perf) heralds = {1: 0} postselect = PostSelect("[0]>0") simulator.set_selection(postselect=postselect, heralds=heralds) simulator.keep_heralds(True) res = simulator.probs_svd(SVDistribution(input_state)) filtered, perf = res["results"], res["global_perf"] theo_filtered, theo_perf = post_select_distribution(non_filtered, postselect, heralds) assert_bsd_close(theo_filtered, filtered) assert theo_perf == pytest.approx(perf) ================================================ FILE: tests/simulators/test_ff_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval import SLOSBackend, BasicState, BSDistribution, NoiseModel, PostSelect from perceval.algorithm import Sampler from perceval.components import BS, Circuit, FFCircuitProvider, Detector, PERM, Processor, catalog from perceval.simulators import FFSimulator from .._test_utils import assert_bsd_close backend = SLOSBackend() sim = FFSimulator(backend) cnot = FFCircuitProvider(2, 0, Circuit(2)).add_configuration([0, 1], PERM([1, 0])) detector = Detector.pnr() def test_basic_circuit(): sim.set_circuit([((0, 1), cnot)]) assert sim.probs(BasicState([1, 0, 1, 0]))[BasicState([1, 0, 1, 0])] == pytest.approx(1.) assert sim.probs(BasicState([1, 0, 0, 1]))[BasicState([1, 0, 0, 1])] == pytest.approx(1.) assert sim.probs(BasicState([0, 1, 1, 0]))[BasicState([0, 1, 0, 1])] == pytest.approx(1.) assert sim.probs(BasicState([0, 1, 0, 1]))[BasicState([0, 1, 1, 0])] == pytest.approx(1.) def test_cascade(): n = 3 circuit_list = [((0, 1), BS())] for i in range(n): circuit_list += [ ((2 * i,), detector), ((2 * i + 1,), detector), ((2 * i, 2 * i + 1), cnot)] sim.set_circuit(circuit_list) input_state = BasicState((n + 1) * [1, 0]) assert_bsd_close(sim.probs(input_state), BSDistribution({ input_state: .5, BasicState((n + 1) * [0, 1]): .5 })), "Incorrect simulated distribution" def test_with_processor(): # Same than test_basic_circuit, but using a Processor and a sampler to get the results proc = Processor("SLOS", 4) proc.min_detected_photons_filter(2) proc.with_input(BasicState([0, 1, 1, 0])) proc.add(0, detector) proc.add(1, detector) proc.add(0, cnot) sampler = Sampler(proc) assert_bsd_close(sampler.probs()["results"], BSDistribution(BasicState([0, 1, 0, 1]))) def test_with_herald(): proc = Processor("SLOS", 5) proc.add(0, BS()) proc.add(1, BS()) proc.add(1, detector) proc.add(2, detector) proc.add(1, cnot) proc.add_herald(0, 0) proc.with_input(BasicState([1, 0, 1, 0])) sampler = Sampler(proc) res = sampler.probs() assert_bsd_close(res["results"], BSDistribution({ BasicState([1, 0, 1, 0]): .5, BasicState([0, 1, 0, 1]): .5, })) assert res["global_perf"] == pytest.approx(.5) def test_with_postselect(): # Same than with heralds proc = Processor("SLOS", 5) proc.add(0, BS()) proc.add(1, BS()) proc.add(1, detector) proc.add(2, detector) proc.add(1, cnot) proc.with_input(BasicState([0, 1, 0, 1, 0])) proc.set_postselection(PostSelect("[0] == 0")) sampler = Sampler(proc) res = sampler.probs() assert_bsd_close(res["results"], BSDistribution({ BasicState([0, 1, 0, 1, 0]): .5, BasicState([0, 0, 1, 0, 1]): .5, })) assert res["global_perf"] == pytest.approx(.5) def test_min_photons_filter(): config = FFCircuitProvider(2, 0, Circuit(2)).add_configuration([0, 1], BS()) proc = Processor(backend, 4) proc.add(0, BS()) proc.add(0, detector) proc.add(1, detector) proc.add(0, config) proc.add(3, Detector.threshold()) proc.min_detected_photons_filter(3) input_state = BasicState([1, 0, 1, 1]) proc.with_input(input_state) # Equivalent to using a BS with two outputs and a post-selection sampler = Sampler(proc) expected_perf = .75 res = sampler.probs() assert_bsd_close(res["results"], BSDistribution({ input_state: .5 / expected_perf, BasicState([0, 1, 2, 0]): .25 / expected_perf })) assert res["global_perf"] == pytest.approx(expected_perf) def test_physical_perf(): # Here the perf is induced by the noise proc = Processor("SLOS", 4, noise=NoiseModel(.5)) proc.add(0, BS()) proc.add(0, detector) proc.add(1, detector) proc.add(0, cnot) proc.min_detected_photons_filter(2) proc.with_input(BasicState([1, 0, 1, 0])) sampler = Sampler(proc) res = sampler.probs() assert_bsd_close(res["results"], BSDistribution({ BasicState([1, 0, 1, 0]): 0.5, BasicState([0, 1, 0, 1]): 0.5 })) assert res["global_perf"] == pytest.approx(0.25) # Here the perf is induced by the circuit and the detectors config = FFCircuitProvider(1, 0, BS()).add_configuration([1], Circuit(2)) proc = Processor("SLOS", 3) proc.add(0, BS()) proc.add(0, Detector.threshold()) proc.add(0, config) proc.add(1, Detector.threshold()) proc.add(2, Detector.threshold()) proc.min_detected_photons_filter(2) proc.with_input(BasicState([1, 0, 1])) sampler = Sampler(proc) res = sampler.probs() assert_bsd_close(res["results"], BSDistribution({ BasicState([1, 0, 1]): 1., })) assert res["global_perf"] == pytest.approx(0.5) def test_with_proc(): proc = Processor("SLOS", 6) cfg = FFCircuitProvider(2, 0, Circuit(4)).add_configuration((0, 1), catalog['postprocessed cnot'].build_processor()) cnot_perf = 1 / 9 proc.add(0, BS()) proc.add(0, detector) proc.add(1, detector) proc.add(0, cfg) proc.min_detected_photons_filter(2) proc.with_input(BasicState([0, 1, 0, 1, 0, 1])) # ending in logical 11 sampler = Sampler(proc) res = sampler.probs() assert_bsd_close(res["results"], BSDistribution({ BasicState([1, 0, 0, 1, 0, 1]): .5 / (.5 + .5 * cnot_perf), BasicState([0, 1, 0, 1, 1, 0]): .5 * cnot_perf / (.5 + .5 * cnot_perf), })) assert res["global_perf"] == pytest.approx(.5 * (1 + cnot_perf)) # Same with heralded processor as default circuit proc = Processor("SLOS", 6) cfg = FFCircuitProvider(2, 0, catalog['postprocessed cnot'].build_processor()) cfg.add_configuration((1, 0), Circuit(4)) cnot_perf = 1 / 9 proc.add(0, BS()) proc.add(0, detector) proc.add(1, detector) proc.add(0, cfg) proc.min_detected_photons_filter(2) proc.with_input(BasicState([0, 1, 0, 1, 0, 1])) # ending in logical 11 sampler = Sampler(proc) res = sampler.probs() assert_bsd_close(res["results"], BSDistribution({ BasicState([1, 0, 0, 1, 0, 1]): .5 / (.5 + .5 * cnot_perf), BasicState([0, 1, 0, 1, 1, 0]): .5 * cnot_perf / (.5 + .5 * cnot_perf), })) assert res["global_perf"] == pytest.approx(.5 * (1 + cnot_perf)) def test_non_adjacent_config(): proc = Processor("SLOS", 6) cnot = FFCircuitProvider(2, 2, Circuit(2)).add_configuration([0, 1], PERM([1, 0])) proc.min_detected_photons_filter(2) proc.with_input(BasicState([0, 1, 0, 0, 1, 0])) proc.add(0, detector) proc.add(1, detector) proc.add(0, cnot) sampler = Sampler(proc) assert_bsd_close(sampler.probs()["results"], BSDistribution(BasicState([0, 1, 0, 0, 0, 1]))) # Negative offset proc = Processor("SLOS", 4) cnot = FFCircuitProvider(2, -1, Circuit(2)).add_configuration([0, 1], PERM([1, 0])) proc.min_detected_photons_filter(2) proc.with_input(BasicState([1, 0, 0, 1])) proc.add(2, detector) proc.add(3, detector) proc.add(2, cnot) sampler = Sampler(proc) assert_bsd_close(sampler.probs()["results"], BSDistribution(BasicState([0, 1, 0, 1]))) def test_with_state_vector(): proc = Processor("SLOS", 4) proc.add(0, detector) proc.add(1, detector) proc.add(0, cnot) input_state = (BasicState([1, 0]) + BasicState([0, 1])) * BasicState([1, 0]) proc.min_detected_photons_filter(2) proc.with_input(input_state) sampler = Sampler(proc) assert_bsd_close(sampler.probs()["results"], BSDistribution({ BasicState([1, 0, 1, 0]): .5, BasicState([0, 1, 0, 1]): .5 })) def test_with_annotated_state_vector(): proc = Processor("SLOS", 5) tri_not = (FFCircuitProvider(2, 0, Circuit(3)) .add_configuration([0, 2], PERM([2, 1, 0])) .add_configuration([1, 1], PERM([1, 2, 0]))) proc.add(0, BS()) proc.add(0, detector) proc.add(1, detector) proc.add(0, tri_not) input_state = (BasicState("|{0}, {1}>") + BasicState("|{0}, {0}>")) * BasicState("|{0}, 0, 0>") proc.min_detected_photons_filter(2) proc.with_input(input_state) sampler = Sampler(proc) assert_bsd_close(sampler.probs()["results"], BSDistribution({ BasicState([2, 0, 1, 0, 0]): .375, BasicState([0, 2, 0, 0, 1]): .375, BasicState([1, 1, 0, 1, 0]): .25 })) def test_config_with_config(): proc = Processor("SLOS", 8) # Note: please don't do this, this is just to test an edge case cnot_proc = Processor("SLOS", 4) cnot_proc.add(0, PERM([1, 0])) cnot_proc.add(0, Detector.pnr()) cnot_proc.add(1, Detector.pnr()) cnot_proc.add(0, cnot) double_not = FFCircuitProvider(2, 0, Circuit(2)).add_configuration([0, 1], cnot_proc) proc.add(0, BS()) proc.add(0, Detector.pnr()) proc.add(1, Detector.pnr()) proc.add(0, double_not) proc.add(4, Detector.pnr()) proc.add(5, Detector.pnr()) proc.add(4, cnot) proc.min_detected_photons_filter(4) proc.with_input(BasicState([1, 0, 1, 0, 1, 0, 1, 0])) sampler = Sampler(proc) assert_bsd_close(sampler.probs()["results"], BSDistribution({ BasicState([1, 0, 1, 0, 1, 0, 1, 0]): .5, BasicState([0, 1, 0, 1, 0, 1, 0, 1]): .5 })) ================================================ FILE: tests/simulators/test_loss_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval import Processor, Unitary, LC, Matrix, BSDistribution, BasicState, NoiseModel, Detector, SVDistribution, \ PostSelect, post_select_distribution from perceval.algorithm import Sampler from perceval.simulators.loss_simulator import LossSimulator from perceval.simulators.simulator import Simulator from perceval.backends._slos import SLOSBackend from .._test_utils import assert_bsd_close U = Matrix.random_unitary(2) loss = .3 input_state = BasicState([1, 1]) def test_lc_minimal(): components = [((0,), LC(loss))] expected_svd = BSDistribution() expected_svd[BasicState([0])] = loss ** 2 expected_svd[BasicState([1])] = 2 * loss * (1 - loss) expected_svd[BasicState([2])] = (1 - loss) ** 2 simu = LossSimulator(Simulator(SLOSBackend())) simu.set_circuit(components) simu.set_min_detected_photons_filter(0) res = simu.probs(BasicState([2])) assert_bsd_close(res, expected_svd) def test_lc_detectors(): components = [((0,), LC(loss))] expected_svd = BSDistribution() expected_svd[BasicState([0])] = loss ** 2 expected_svd[BasicState([1])] = 1 - loss ** 2 simu = LossSimulator(Simulator(SLOSBackend())) simu.set_circuit(components) simu.set_min_detected_photons_filter(0) detector = Detector.threshold() assert simu._prepare_detectors([detector]) == [detector, None] res = simu.probs_svd(SVDistribution(BasicState([2])), [detector]) assert_bsd_close(res["results"], expected_svd) def test_lc_commutative(): # All LC on the input or on the output of the processor yield the same results components_1 = [((0, 1), Unitary(U)), ((0,), LC(loss)), ((1,), LC(loss))] components_2 = [((0,), LC(loss)), ((1,), LC(loss)), ((0, 1), Unitary(U))] simu = LossSimulator(Simulator(SLOSBackend())) simu.set_circuit(components_1) simu.set_min_detected_photons_filter(0) res_1 = simu.probs(input_state) simu.set_circuit(components_2) res_2 = simu.probs(input_state) assert_bsd_close(res_1, res_2) def test_lc_source_losses_equivalence(): # When the losses are balanced p = Processor("SLOS", Unitary(U), NoiseModel(transmittance=1 - loss)) p.with_input(input_state) p.min_detected_photons_filter(0) components_1 = [((0, 1), Unitary(U)), ((0,), LC(loss)), ((1,), LC(loss))] simu = LossSimulator(Simulator(SLOSBackend())) simu.set_circuit(components_1) simu.set_min_detected_photons_filter(0) res_1 = simu.probs(input_state) sampler = Sampler(p) real_out = sampler.probs()["results"] assert_bsd_close(real_out, res_1) def test_lc_perf(): p = Processor("SLOS", 2) p.add(1, LC(loss)) p.add_herald(1, 1) p.min_detected_photons_filter(1) p.compute_physical_logical_perf(True) p.with_input(BasicState([1])) res = p.probs() assert res["logical_perf"] == pytest.approx(1), "Wrong logical perf with LC and heralds" assert res["physical_perf"] == pytest.approx(1 - loss), "Wrong physical_perf with LC and heralds" def test_logical_selection(): backend = SLOSBackend() simulator = LossSimulator(Simulator(backend)) input_state = BasicState([1, 0]) input_circ = [((0, 1), Unitary(U)), ((0,), LC(loss)), ((1,), LC(loss / 2))] # Break symmetry simulator.set_circuit(input_circ) non_filtered = simulator.probs(input_state) heralds = {} postselect = PostSelect('[1]>0') simulator.set_selection(postselect=postselect, heralds=heralds) res = simulator.probs_svd(SVDistribution(input_state)) filtered, perf = res["results"], res["global_perf"] theo_filtered, theo_perf = post_select_distribution(non_filtered, postselect, heralds) assert_bsd_close(theo_filtered, filtered) assert theo_perf == pytest.approx(perf) heralds = {1: 0} postselect = PostSelect() simulator.set_selection(postselect=postselect, heralds=heralds) simulator.keep_heralds(False) res = simulator.probs_svd(SVDistribution(input_state)) filtered, perf = res["results"], res["global_perf"] theo_filtered, theo_perf = post_select_distribution(non_filtered, postselect, heralds, False) assert_bsd_close(theo_filtered, filtered) assert theo_perf == pytest.approx(perf) heralds = {1: 0} postselect = PostSelect("[0]>0") simulator.set_selection(postselect=postselect, heralds=heralds) simulator.keep_heralds(True) res = simulator.probs_svd(SVDistribution(input_state)) filtered, perf = res["results"], res["global_perf"] theo_filtered, theo_perf = post_select_distribution(non_filtered, postselect, heralds) assert_bsd_close(theo_filtered, filtered) assert theo_perf == pytest.approx(perf) ================================================ FILE: tests/simulators/test_noisy_sampling_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.simulators import NoisySamplingSimulator from perceval.backends import Clifford2017Backend from perceval.components import Unitary, Source, BS, Detector from perceval.utils import Matrix, BasicState, SVDistribution import pytest @pytest.mark.parametrize("max_samples, max_shots", [(100, None), (100, 10), (10, 100)]) def test_perfect_sampling(max_samples, max_shots): sim = _build_noisy_simulator(8) input_state = SVDistribution(BasicState([1, 0]*4)) sim.compute_physical_logical_perf(True) sampling = sim.samples(input_state, max_samples, max_shots) assert sampling['physical_perf'] == 1 assert sampling['logical_perf'] == 1 assert len(sampling['results']) == max_samples if max_shots is None else min(max_samples, max_shots) for output_state in sampling['results']: assert output_state.n == 4 @pytest.mark.parametrize("max_samples, max_shots", [(100, None), (100, 10), (10, 100)]) def test_perfect_sampling_source(max_samples, max_shots): sim = _build_noisy_simulator(8) input_state = BasicState([1, 0]*4) sim.compute_physical_logical_perf(True) sampling = sim.samples((Source(), input_state), max_samples, max_shots) assert sampling['physical_perf'] == 1 assert sampling['logical_perf'] == 1 assert len(sampling['results']) == max_samples if max_shots is None else min(max_samples, max_shots) for output_state in sampling['results']: assert output_state.n == 4 def _build_noisy_simulator(size: int): c = Unitary(Matrix.random_unitary(size)) sim = NoisySamplingSimulator(Clifford2017Backend()) sim.set_circuit(c) return sim def test_sample_0_samples(): sim = _build_noisy_simulator(6) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = source.generate_distribution(BasicState([1, 0] * 3)) sampling = sim.samples(input_state, 0) assert len(sampling['results']) == 0 def test_sample_0_samples_source(): sim = _build_noisy_simulator(6) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = BasicState([1, 0] * 3) sampling = sim.samples((source, input_state), 0) assert len(sampling['results']) == 0 def test_noisy_sampling(): sim = _build_noisy_simulator(6) sim.compute_physical_logical_perf(True) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = source.generate_distribution(BasicState([1, 0] * 3)) sampling = sim.samples(input_state, 100) assert sampling['physical_perf'] == 1 assert sampling['logical_perf'] == 1 assert len(sampling['results']) == 100 sim.set_min_detected_photons_filter(2) sampling = sim.samples(input_state, 100) assert sampling['physical_perf'] < 1 assert sampling['logical_perf'] == 1 assert len(sampling['results']) == 100 # test sample_count too sampling = sim.sample_count(input_state, 100) assert sampling['physical_perf'] < 1 assert sampling['logical_perf'] == 1 assert sampling['results'].total() == 100 def test_noisy_sampling_source(): sim = _build_noisy_simulator(6) sim.compute_physical_logical_perf(True) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = BasicState([1, 0] * 3) sampling = sim.samples((source, input_state), 100) assert sampling['physical_perf'] == pytest.approx(1) assert sampling['logical_perf'] == 1 assert len(sampling['results']) == 100 sim.set_min_detected_photons_filter(2) sampling = sim.samples((source, input_state), 100) assert sampling['physical_perf'] < 1 assert sampling['logical_perf'] == 1 assert len(sampling['results']) == 100 # test sample_count too sampling = sim.sample_count((source, input_state), 100) assert sampling['physical_perf'] < 1 assert sampling['logical_perf'] == 1 assert sampling['results'].total() == 100 def test_noisy_sampling_with_heralds(): sim = _build_noisy_simulator(6) sim.compute_physical_logical_perf(True) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = source.generate_distribution(BasicState([1, 0] * 3)) sim.set_min_detected_photons_filter(2) sim.set_selection(heralds={0: 0}) sampling = sim.samples(input_state, 100) assert sampling['physical_perf'] < 1 assert sampling['logical_perf'] < 1 assert len(sampling['results']) == 100 for output_state in sampling['results']: assert len(output_state) == 6 assert output_state[0] == 0 # Fixed by the heralding sim.keep_heralds(False) sampling = sim.sample_count(input_state, 100) assert sampling['logical_perf'] < 1 for output_state in sampling['results']: assert len(output_state) == 5 # The ancillary mode was removed from all output states def test_noisy_sampling_with_detectors(): simulator = NoisySamplingSimulator(Clifford2017Backend()) simulator.set_circuit(BS()) simulator.set_detectors([Detector.pnr(), Detector.pnr()]) simulator.compute_physical_logical_perf(True) # Perfect sampling with perfect detectors sampling = simulator.samples(SVDistribution(BasicState([1, 1])), 100, 100) assert sampling["physical_perf"] == 1 assert sampling["logical_perf"] == 1 assert all([state == BasicState([2, 0]) or state == BasicState([0, 2]) for state in sampling['results']]) # Go to "generic" algo with at least one noisy detector simulator.set_detectors([Detector.pnr(), Detector.threshold()]) sampling = simulator.samples(SVDistribution(BasicState([1, 1])), 100, 100) assert sampling["physical_perf"] == 1 assert sampling["logical_perf"] == 1 assert all([state == BasicState([2, 0]) or state == BasicState([0, 1]) for state in sampling['results']]) simulator.set_selection(min_detected_photons_filter=2) sampling = simulator.samples(SVDistribution(BasicState([1, 1])), 100, 100) assert sampling["physical_perf"] < 1 assert sampling["logical_perf"] == 1 assert all([state == BasicState([2, 0]) for state in sampling['results']]) ================================================ FILE: tests/simulators/test_polarization_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from unittest.mock import patch from perceval import Detector from perceval.simulators.polarization_simulator import PolarizationSimulator from perceval.simulators.simulator import Simulator from perceval.backends import NaiveBackend, BackendFactory from perceval.components import Circuit, BS, PBS, PERM, PS, PR, HWP from perceval.utils import BasicState, StateVector, AnnotatedFockState from .._test_utils import assert_sv_close, LogChecker import pytest import math from perceval.utils.logging import ExqaliburLogger def _oracle(mark): """Values 0, 1, 2 and 3 for parameter 'mark' respectively mark the elements "00", "01", "10" and "11" of the list.""" oracle_circuit = Circuit(m=2, name='Oracle') # The following dictionary translates n into the corresponding component settings oracle_dict = {0: (1, 0), 1: (0, 1), 2: (1, 1), 3: (0, 0)} PC_state, LC_state = oracle_dict[mark] # Mode b if PC_state == 1: oracle_circuit //= HWP(0) oracle_circuit.add(0, PR(math.pi/2)) if LC_state == 1: oracle_circuit //= HWP(0) # Mode a if LC_state == 1: oracle_circuit //= (1, HWP(0)) if PC_state == 1: oracle_circuit //= (1, HWP(0)) return oracle_circuit def _hwp(xsi): hwp = Circuit(m=1) hwp.add(0, HWP(xsi)).add(0, PS(-math.pi / 2)) return hwp def _grover_circuit(mark): init_circuit = (Circuit(m=2, name="Initialization") // _hwp(math.pi / 8) // BS.Ry() // PS(-math.pi)) inversion_circuit = (Circuit(m=2, name='Inversion') // BS.Ry() // _hwp(math.pi / 4) // BS.Ry()) detection_circuit = Circuit(m=4, name='Detection') detection_circuit.add((0, 1), PBS()) detection_circuit.add((2, 3), PBS()) grover_circuit = Circuit(m=4, name='Grover') grover_circuit.add(0, init_circuit).add(0, _oracle(mark)).add(0, inversion_circuit) grover_circuit.add(1, PERM([1, 0])).add(0, detection_circuit) return grover_circuit def test_grover(): psimu = PolarizationSimulator(Simulator(NaiveBackend())) input_state = BasicState("|{P:H},0,0,0>") expected = { BasicState([0, 0, 0, 1]): _grover_circuit(0), BasicState([0, 0, 1, 0]): _grover_circuit(1), BasicState([0, 1, 0, 0]): _grover_circuit(2), BasicState([1, 0, 0, 0]): _grover_circuit(3) } for expected_output, circuit in expected.items(): psimu.set_circuit(circuit) result_dist = psimu.probs(input_state) assert len(result_dist) == 1 assert result_dist[expected_output] == pytest.approx(1) def test_polarization_evolve(): psimu = PolarizationSimulator(Simulator(NaiveBackend())) input_state = BasicState("|{P:H}>") circuit = PR(delta=math.pi/4) psimu.set_circuit(circuit) sv_out = psimu.evolve(input_state) expected = StateVector(AnnotatedFockState("|{P:H}>")) - StateVector(AnnotatedFockState("|{P:V}>")) assert_sv_close(sv_out, expected) @pytest.mark.parametrize("backend_name", ["Naive", "SLOS", "MPS"]) def test_polarization_circuit_0(backend_name): c = HWP(math.pi/4) psimu = PolarizationSimulator(Simulator(BackendFactory.get_backend(backend_name))) psimu.set_circuit(c) res = psimu.probs(BasicState("|{P:H}>")) assert len(res) == 1 assert res[BasicState("|1>")] == pytest.approx(1) assert_sv_close(psimu.evolve(BasicState("|{P:H}>")), complex(0,1)*StateVector(AnnotatedFockState('|{P:V}>'))) assert_sv_close(psimu.evolve(BasicState("|{P:V}>")), complex(0,1)*StateVector(AnnotatedFockState('|{P:H}>'))) assert_sv_close(psimu.evolve(BasicState("|{P:D}>")), complex(0,1)*(StateVector(AnnotatedFockState('|{P:H}>')) + StateVector(AnnotatedFockState('|{P:V}>')))) # assert str(psimu.evolve(BasicState("|{P:A}>"))) == '|{P:A}>' # P:A isn't properly dealt with anymore @patch.object(ExqaliburLogger, "warn") def test_polarization_detector(mock_warn): psimu = PolarizationSimulator(Simulator(NaiveBackend())) with LogChecker(mock_warn): assert psimu._prepare_detectors([Detector.ppnr(3)]) is None with LogChecker(mock_warn, expected_log_number=0): assert psimu._prepare_detectors([Detector.pnr()]) is None ================================================ FILE: tests/simulators/test_samples_provider.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval import NoisyFockState from perceval.simulators.noisy_sampling_simulator import SamplesProvider from perceval.backends import Clifford2017Backend from perceval.components import Circuit, Source from perceval.utils import BasicState, NoiseModel, BSDistribution def _svd_to_bsd(svd): res = BSDistribution() for state, prob in svd.items(): if isinstance(state[0], NoisyFockState): bs_list = state[0].separate_state() else: bs_list = [state[0]] for bs in bs_list: res.add(bs, prob) return res size = 2 clifford = Clifford2017Backend() clifford.set_circuit(Circuit(size)) # Identity circuit ideal_input = BasicState([1]*size) source = Source.from_noise_model(NoiseModel(transmittance=0.2, g2=0.05, indistinguishability=0.75)) possible_fock_input = [BasicState([0, 0]), BasicState([1, 0]), BasicState([0, 1]), BasicState([1, 1])] def test_samples_provider_distribution(): noisy_input = source.generate_distribution(ideal_input) provider = SamplesProvider(clifford) provider.estimate_weights_from_distribution(noisy_input, 1000) provider.prepare() assert provider._pools and provider._weights for state in possible_fock_input: assert state in provider._pools and state in provider._weights res = provider.sample_from(state) assert res == state def test_samples_provider_source(): provider = SamplesProvider(clifford) sampler = lambda i: source.generate_separated_samples(i, ideal_input, 0) provider.estimate_weights_from_source(sampler, 1000) provider.prepare() assert provider._pools and provider._weights for state in possible_fock_input: assert state in provider._pools and state in provider._weights res = provider.sample_from(state) assert res == state ================================================ FILE: tests/simulators/test_simulate_detectors.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval.simulators._simulate_detectors import simulate_detectors, simulate_detectors_sample from perceval.components import Detector, BSLayeredPPNR from perceval.utils import BSDistribution, BasicState, BSSamples, samples_to_sample_count def test_simulate_detectors(): pnr_detector_list = [Detector.pnr()] * 3 # Only PNR detectors thr_detector_list = [Detector.threshold()] * 3 # Only threshold detectors mixed_detector_list = [BSLayeredPPNR(1), Detector.pnr(), Detector.threshold()] bsd = BSDistribution({ BasicState([1, 1, 1]): 0.2, BasicState([2, 0, 1]): 0.3, BasicState([2, 1, 0]): 0.25, BasicState([0, 0, 3]): 0.15, BasicState([1, 0, 2]): 0.1 }) assert simulate_detectors(bsd, pnr_detector_list)[0] == bsd res, _ = simulate_detectors(bsd, thr_detector_list) assert len(res) == 4 assert res[BasicState([1, 1, 1])] == 0.2 assert res[BasicState([1, 0, 1])] == 0.4 assert res[BasicState([0, 0, 1])] == 0.15 assert res[BasicState([1, 1, 0])] == 0.25 res, _ = simulate_detectors(bsd, mixed_detector_list) assert res[BasicState([1, 1, 1])] == pytest.approx(0.2) assert res[BasicState([1, 0, 1])] == pytest.approx(0.3 * 0.5 + 0.1) assert res[BasicState([2, 0, 1])] == pytest.approx(0.3 * 0.5) assert res[BasicState([1, 1, 0])] == pytest.approx(0.25 * 0.5) assert res[BasicState([2, 1, 0])] == pytest.approx(0.25 * 0.5) assert res[BasicState([0, 0, 1])] == pytest.approx(0.15) res, _ = simulate_detectors(bsd, mixed_detector_list, prob_threshold=0.6) assert res[BasicState([1, 1, 1])] == pytest.approx(4/9) assert res[BasicState([0, 0, 1])] == pytest.approx(1/3) assert res[BasicState([1, 0, 1])] == pytest.approx(2/9) def test_simulate_detectors_sample(): bss_in = BSSamples([BasicState([2, 2, 2]), BasicState([2, 0, 3]), BasicState([1, 1, 1]), BasicState([0, 0, 0])]) pnr = Detector.pnr() thr = Detector.threshold() expected = [BasicState([2, 1, 1]), BasicState([2, 0, 1]), BasicState([1, 1, 1]), BasicState([0, 0, 0])] for s_in, s_expected in zip(bss_in, expected): s_out = simulate_detectors_sample(s_in, [pnr, thr, thr]) assert s_out == s_expected def test_simulate_detectors_sample_ppnr(): # PPNR creates multiple possibilities the detector simulation algo needs to sample from bs_in = BasicState([2, 2]) ppnr_detector = BSLayeredPPNR(1) bss_out = BSSamples() for i in range(1000): bss_out.append(simulate_detectors_sample(bs_in, [ppnr_detector]*2)) bsc_out = samples_to_sample_count(bss_out) assert len(bsc_out) == 4 assert BasicState([2, 2]) in bsc_out assert BasicState([2, 1]) in bsc_out assert BasicState([1, 2]) in bsc_out assert BasicState([1, 1]) in bsc_out ================================================ FILE: tests/simulators/test_simulator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest from perceval import catalog, NoisyFockState, AnnotatedFockState from perceval.backends import AStrongSimulationBackend, SLOSBackend from perceval.simulators import Simulator from perceval.components import Circuit, BS, PS, Source, unitary_components from perceval.utils import BasicState, BSDistribution, StateVector, SVDistribution, PostSelect, Matrix, DensityMatrix from .._test_utils import assert_sv_close, assert_svd_close class MockBackend(AStrongSimulationBackend): @property def name(self) -> str: return "Mock" def prob_amplitude(self, output_state: BasicState) -> complex: return 0 def prob_distribution(self) -> BSDistribution: n = self._input_state.n m = self._input_state.m output_state = [0]*m output_state[(n-1) % m] = n return BSDistribution(BasicState(output_state)) def evolve(self) -> StateVector: n = self._input_state.n m = self._input_state.m output_state = [0] * m output_state[(n-1) % m] = n return StateVector(output_state) def test_simulator_probs_mock(): input_state = BasicState([1,1,1]) simulator = Simulator(MockBackend()) simulator.set_circuit(Circuit(3)) output_dist = simulator.probs(input_state) assert len(output_dist) == 1 assert list(output_dist.keys())[0] == BasicState([0, 0, 3]) assert simulator.DEBUG_evolve_count == 1 input_state = BasicState('|{1},{2},{3}>') output_dist = simulator.probs(input_state) assert len(output_dist) == 1 assert list(output_dist.keys())[0] == BasicState([3, 0, 0]) assert simulator.DEBUG_evolve_count == 4 input_state = BasicState('|{1}{2}{3},0,0>') output_dist = simulator.probs(input_state) assert len(output_dist) == 1 assert list(output_dist.keys())[0] == BasicState([3, 0, 0]) assert simulator.DEBUG_evolve_count == 4 def test_simulator_probs_svd_indistinguishable(): svd = SVDistribution() svd[StateVector([1,0]) + StateVector([0,1])] = 0.3 svd[StateVector([1,1]) + 1j*StateVector([0,1])] = 0.3 svd[StateVector('|2,0>') + StateVector([1,1])] = 0.4 simulator = Simulator(SLOSBackend()) simulator.set_circuit(BS()) res = simulator.probs_svd(svd)['results'] assert len(res) == 5 assert res[BasicState("|1,0>")] == pytest.approx(0.225) assert res[BasicState("|0,1>")] == pytest.approx(0.225) assert res[BasicState("|2,0>")] == pytest.approx(0.225) assert res[BasicState("|0,2>")] == pytest.approx(0.225) assert res[BasicState("|1,1>")] == pytest.approx(0.1) # remove the |0, 1> state from the second sv, and the first sv simulator.set_min_detected_photons_filter(2) res = simulator.probs_svd(svd) assert res["global_perf"] == pytest.approx(0.55) assert len(res["results"]) == 3 def test_simulator_probs_svd_distinguishable(): in_svd = SVDistribution({ StateVector(BasicState('|{0}{1},{0}>')): 1 }) circuit = BS.H(theta=BS.r_to_theta(0.4)) sim = Simulator(SLOSBackend()) sim.set_circuit(circuit) res = sim.probs_svd(in_svd)['results'] assert len(res) == 4 assert res[BasicState("|3,0>")] == pytest.approx(0.192) assert res[BasicState("|2,1>")] == pytest.approx(0.304) assert res[BasicState("|1,2>")] == pytest.approx(0.216) assert res[BasicState("|0,3>")] == pytest.approx(0.288) def test_simulator_probs_svd_superposed(): superposed_state = StateVector(NoisyFockState("|0,{0},{1},0>")) + StateVector(NoisyFockState("|0,{1},{0},0>")) in_svd = SVDistribution({superposed_state: 1}) circuit = Circuit(4) circuit.add(1, BS.H()).add(0, BS.H(BS.r_to_theta(1/3), phi_tl=-math.pi / 2, phi_bl=math.pi, phi_tr=math.pi / 2)) circuit.add(2, BS.H(BS.r_to_theta(1/3))).add(1, BS.H()) sim = Simulator(SLOSBackend()) sim.set_circuit(circuit) res = sim.probs_svd(in_svd)['results'] assert len(res) == 7 assert res[BasicState("|2,0,0,0>")] == pytest.approx(2/9) assert res[BasicState("|0,0,0,2>")] == pytest.approx(2/9) assert res[BasicState("|1,0,1,0>")] == pytest.approx(1/9) assert res[BasicState("|1,1,0,0>")] == pytest.approx(1/9) assert res[BasicState("|0,1,1,0>")] == pytest.approx(1/9) assert res[BasicState("|0,1,0,1>")] == pytest.approx(1/9) assert res[BasicState("|0,0,1,1>")] == pytest.approx(1/9) def test_simulator_probs_distinguishable(): in_state = BasicState('|{0}{1},{0}>') circuit = BS.H(theta=BS.r_to_theta(0.4)) sim = Simulator(SLOSBackend()) sim.set_circuit(circuit) res = sim.probs(in_state) assert len(res) == 4 assert res[BasicState("|3,0>")] == pytest.approx(0.192) assert res[BasicState("|2,1>")] == pytest.approx(0.304) assert res[BasicState("|1,2>")] == pytest.approx(0.216) assert res[BasicState("|0,3>")] == pytest.approx(0.288) def test_simulator_probs_postselection(): input_state = BasicState([1, 1, 1]) ps = PostSelect("[2] < 2") # At most 1 photon on mode #2 simulator = Simulator(MockBackend()) simulator.set_postselection(ps) simulator.set_circuit(Circuit(3)) output_dist = simulator.probs(input_state) assert len(output_dist) == 0 assert simulator.logical_perf == pytest.approx(0) input_state = BasicState('|{1},{2},{3}>') output_dist = simulator.probs(input_state) assert len(output_dist) == 1 assert list(output_dist.keys())[0] == BasicState([3, 0, 0]) assert simulator.logical_perf == pytest.approx(1) input_state = BasicState('|{1}{2}{3},0,0>') output_dist = simulator.probs(input_state) assert len(output_dist) == 1 assert list(output_dist.keys())[0] == BasicState([3, 0, 0]) assert simulator.logical_perf == pytest.approx(1) def test_simulator_probampli(): input_state = BasicState("|{0},{1}>") simulator = Simulator(SLOSBackend()) simulator.set_circuit(BS()) assert simulator.prob_amplitude(input_state, BasicState("|{0}{1},0>")) == pytest.approx(0.5j) assert simulator.prob_amplitude(input_state, BasicState("|0,{0}{1}>")) == pytest.approx(0.5j) assert simulator.prob_amplitude(input_state, BasicState("|{0},{1}>")) == pytest.approx(0.5) assert simulator.prob_amplitude(input_state, BasicState("|{1},{0}>")) == pytest.approx(-0.5) assert simulator.prob_amplitude(input_state, BasicState("|2,0>")) == pytest.approx(0) assert simulator.prob_amplitude(input_state, BasicState("|1,1>")) == pytest.approx(0) # prob_amplitude call is strict on annotations name assert simulator.prob_amplitude(input_state, BasicState("|{0}{2},0>")) == pytest.approx(0) input_state = StateVector(NoisyFockState("|{0},{1}>")) assert simulator.prob_amplitude(input_state, BasicState("|{0}{1},0>")) == pytest.approx(0.5j) assert simulator.prob_amplitude(input_state, BasicState("|0,{0}{1}>")) == pytest.approx(0.5j) assert simulator.prob_amplitude(input_state, BasicState("|{0},{1}>")) == pytest.approx(0.5) assert simulator.prob_amplitude(input_state, BasicState("|{1},{0}>")) == pytest.approx(-0.5) assert simulator.prob_amplitude(input_state, BasicState("|2,0>")) == pytest.approx(0) assert simulator.prob_amplitude(input_state, BasicState("|1,1>")) == pytest.approx(0) # prob_amplitude call is strict on annotations name assert simulator.prob_amplitude(input_state, BasicState("|{0}{2},0>")) == pytest.approx(0) def test_simulator_probability(): input_state = BasicState("|{0},{1}>") simulator = Simulator(SLOSBackend()) simulator.set_circuit(BS()) # Output annotations are ignored for a probability call assert simulator.probability(input_state, BasicState("|{0}{1},0>").clear_annotations()) == pytest.approx(0.25) assert simulator.probability(input_state, BasicState("|2,0>")) == pytest.approx(0.25) assert simulator.probability(input_state, BasicState("|0,2>")) == pytest.approx(0.25) assert simulator.probability(input_state, BasicState("|1,1>")) == pytest.approx(0.5) input_state = BasicState("|1,1>") assert simulator.probability(input_state, BasicState("|{0}{1},0>").clear_annotations()) == pytest.approx(0.5) assert simulator.probability(input_state, BasicState("|2,0>")) == pytest.approx(0.5) assert simulator.probability(input_state, BasicState("|0,2>")) == pytest.approx(0.5) assert simulator.probability(input_state, BasicState("|1,1>")) == pytest.approx(0.0) input_state = StateVector(NoisyFockState("|{0},{1}>")) assert simulator.probability(input_state, BasicState("|{0}{1},0>").clear_annotations()) == pytest.approx(0.25) assert simulator.probability(input_state, BasicState("|2,0>")) == pytest.approx(0.25) assert simulator.probability(input_state, BasicState("|0,2>")) == pytest.approx(0.25) assert simulator.probability(input_state, BasicState("|1,1>")) == pytest.approx(0.5) def test_simulator_probs_sv(): st1 = StateVector("|0,1>") st2 = StateVector("|1,0>") sv = st1 + st2 simulator = Simulator(SLOSBackend()) c = BS.H() simulator.set_circuit(c) result = simulator.probs(sv) assert len(result) == 1 assert result[BasicState("|1,0>")] == pytest.approx(1) input_state = BasicState("|{0},{1}>") + BasicState([1, 1]) simulator.set_circuit(c) result = simulator.probs(input_state) assert len(result) == 3 assert result[BasicState("|2,0>")] == pytest.approx(3/8) assert result[BasicState("|0,2>")] == pytest.approx(3/8) assert result[BasicState("|1,1>")] == pytest.approx(1/4) simulator.set_circuit(BS()) s_boson = StateVector(NoisyFockState("|{0},{1}>")) + StateVector(NoisyFockState("|{1},{0}>")) s_fermion = StateVector(NoisyFockState("|{0},{1}>")) - StateVector(NoisyFockState("|{1},{0}>")) result_boson = simulator.probs(s_boson) assert len(result_boson) == 2 assert result_boson[BasicState("|2,0>")] == pytest.approx(1/2) assert result_boson[BasicState("|0,2>")] == pytest.approx(1/2) result_fermion = simulator.probs(s_fermion) assert len(result_fermion) == 1 assert result_fermion[BasicState("|1,1>")] == pytest.approx(1) result2_2 = simulator.probs(BasicState("|2,2>")) assert len(result2_2) == 3 assert result2_2[BasicState("|4,0>")] == pytest.approx(0.375) assert result2_2[BasicState("|2,2>")] == pytest.approx(0.25) assert result2_2[BasicState("|0,4>")] == pytest.approx(0.375) def test_evolve_indistinguishable(): simulator = Simulator(SLOSBackend()) simulator.set_circuit(BS.H()) sv1 = BasicState([1, 1]) sv1_out = simulator.evolve(sv1) assert_sv_close(sv1_out, math.sqrt(2)/2*StateVector([2, 0]) - math.sqrt(2)/2*StateVector([0, 2])) sv1_out_out = simulator.evolve(sv1_out) assert_sv_close(sv1_out_out, StateVector([1, 1])) def test_evolve_distinguishable(): simulator = Simulator(SLOSBackend()) simulator.set_circuit(BS.H()) sv2 = StateVector(NoisyFockState("|{0},{0}{1}>")) sv2_out = simulator.evolve(sv2) assert pytest.approx(sv2_out[BasicState('|2{0}{1},0>')]) == 1/2 assert pytest.approx(sv2_out[BasicState('|2{0},{1}>')]) == -1/2 assert pytest.approx(sv2_out[BasicState('|{1},2{0}>')]) == -1/2 assert pytest.approx(sv2_out[BasicState('|0,2{0}{1}>')]) == 1/2 sv2_out_out = simulator.evolve(sv2_out) assert_sv_close(sv2_out_out, sv2) def test_evolve_phase(): input_state = StateVector([2, 0]) + StateVector([1, 1]) c = Circuit(2).add(1, PS(phi=math.pi/3)) simu = Simulator(SLOSBackend()) simu.set_circuit(c) output_sv = simu.evolve(input_state) assert output_sv[BasicState([1, 1])] == pytest.approx(complex(math.sqrt(2)/4, math.sqrt(6)/4)) input_state2 = StateVector([0, 0]) assert simu.evolve(input_state2) == StateVector([0,0]) def test_simulator_evolve_svd(): input_svd = SVDistribution({StateVector([1, 1]): 0.2, StateVector([2, 0]): 0.8}) b = SLOSBackend() b.set_circuit(Circuit(2).add(0, BS.H())) sim = Simulator(b) svd_expected = SVDistribution({(math.sqrt(2)/2)*BasicState([2,0])-(math.sqrt(2)/2)*BasicState([0,2]): 0.2, 0.5*BasicState([2,0])+0.5*BasicState([0,2])+(math.sqrt(2)/2)*BasicState([1,1]): 0.8}) assert_svd_close(sim.evolve_svd(input_svd)['results'], svd_expected) ps = PostSelect("[0] == 1") sv = BasicState([0, 1]) + BasicState([1, 0]) sv.normalize() input_svd_2 = SVDistribution({sv: 0.2, StateVector([1,0]): 0.8}) sim.set_postselection(ps) output_svd_2 = sim.evolve_svd(input_svd_2)["results"] assert len(output_svd_2) == 1 assert output_svd_2[StateVector([1,0])] == pytest.approx(1) def test_probs_svd_with_heralds(): sim = Simulator(SLOSBackend()) sim.set_circuit(Circuit(4) // BS() // (2, BS())) heralds = {1: 0, 3: 0} sim.set_selection(heralds=heralds) sim.compute_physical_logical_perf(True) results = sim.probs_svd(SVDistribution(BasicState([1]*4))) assert results["logical_perf"] == pytest.approx(0.5 ** 2) assert results["results"][BasicState([2, 0, 2, 0])] == 1 sim.set_min_detected_photons_filter(4) results = sim.probs_svd(SVDistribution({ BasicState([1, 0, 0, 0]): 0.4, BasicState([1, 1, 0, 0]): 0.3, BasicState([1, 1, 1, 0]): 0.2, BasicState([1, 1, 1, 1]): 0.1, })) assert results["physical_perf"] == pytest.approx(0.1) assert results["logical_perf"] == pytest.approx(0.5 ** 2) assert results["results"][BasicState([2, 0, 2, 0])] == 1 sim.set_min_detected_photons_filter(0) results = sim.probs_svd(SVDistribution({ BasicState([1, 0, 0, 0]) + BasicState([0, 0, 1, 0]): 0.4, BasicState([0, 2, 0, 0]) - BasicState([2, 0, 0, 0]): 0.6, # The photons are outputted together })) assert results["physical_perf"] == pytest.approx(1) assert results["logical_perf"] == pytest.approx(0.5) assert results["results"][BasicState([2, 0, 0, 0])] == pytest.approx(.6) superposed_state = StateVector(NoisyFockState("|0,{0},{1},0>")) + StateVector(NoisyFockState("|0,{1},{0},0>")) in_svd = SVDistribution({superposed_state: 1}) circuit = Circuit(4) circuit.add(1, BS.H()).add(0, BS.H(BS.r_to_theta(1 / 3), phi_tl=-math.pi / 2, phi_bl=math.pi, phi_tr=math.pi / 2)) circuit.add(2, BS.H(BS.r_to_theta(1 / 3))).add(1, BS.H()) sim = Simulator(SLOSBackend()) sim.set_circuit(circuit) sim.set_selection(heralds={1: 0, 2: 0}) sim.compute_physical_logical_perf(True) res = sim.probs_svd(in_svd) assert len(res['results']) == 2 assert res['results'][BasicState("|2,0,0,0>")] == pytest.approx(.5) assert res['results'][BasicState("|0,0,0,2>")] == pytest.approx(.5) assert res['logical_perf'] == pytest.approx(4 / 9) assert res['physical_perf'] == pytest.approx(1) def test_evolve_with_heralds(): sim = Simulator(SLOSBackend()) sim.set_circuit(catalog['heralded cnot'].build_circuit()) heralds = {4: 1, 5: 1} sim.set_selection(heralds=heralds) input_state = StateVector("|0,1,0,1,1,1>") + StateVector("|0,1,1,0,1,1>") assert_sv_close(sim.evolve(input_state), input_state) sim.keep_heralds(False) assert_sv_close(sim.evolve(input_state), StateVector("|0,1,0,1>") + StateVector("|0,1,1,0>")) input_state = BasicState("|0,1,0,1,1,1>") sim.keep_heralds(True) assert_sv_close(sim.evolve(input_state), StateVector("|0,1,1,0,1,1>")) sim.keep_heralds(False) assert_sv_close(sim.evolve(input_state), StateVector("|0,1,1,0>")) brightness = .9 s = Source(brightness) svd = s.generate_distribution(input_state) sim.keep_heralds(True) keep_heralds_output = sim.evolve_svd(svd) assert keep_heralds_output['results'].m == 6 sim.keep_heralds(False) discard_heralds_output = sim.evolve_svd(svd) assert discard_heralds_output['results'].m == 4 assert_svd_close(keep_heralds_output['results'], discard_heralds_output['results'] * StateVector([1, 1])) sim.set_min_detected_photons_filter(2) sim.compute_physical_logical_perf(True) result = sim.evolve_svd(svd) assert_svd_close(result["results"], SVDistribution(BasicState([0, 1, 1, 0]))) assert result["physical_perf"] == pytest.approx(brightness ** 4) assert result["logical_perf"] == pytest.approx(2 / 27) def test_evolve_change_heralds(): sim = Simulator(SLOSBackend()) sim.set_circuit(Circuit(2).add(0, BS())) heralds = {1: 0} sim.set_selection(heralds=heralds) res = sim.evolve_svd(SVDistribution(BasicState([1, 0]))) assert len(next(iter(res["results"]))) == 1 sim.set_selection(heralds={}) res = sim.evolve_svd(SVDistribution(BasicState([1, 0]))) assert len(next(iter(res["results"]))) == 2 def get_comparison_setup(): s = Source(.9) svd = s.generate_distribution(BasicState([1, 1, 1, 1])) U = Matrix.random_unitary(4) circuit = Circuit(4).add(0, unitary_components.Unitary(U)) sim = Simulator(SLOSBackend()) sim.set_circuit(circuit) dm = DensityMatrix.from_svd(svd) return sim, dm, svd def test_evolve_density_matrix(): sim, dm, svd = get_comparison_setup() final_svd = sim.evolve_svd(svd)["results"] final_dm = sim.evolve_density_matrix(dm) comparing_dm = DensityMatrix.from_svd(final_svd) assert max((final_dm.mat-comparing_dm.mat).data) < 1e-10 def test_probs_density_matrix(): sim, dm, svd = get_comparison_setup() probs_1 = sim.probs_svd(svd)["results"] probs_2 = sim.probs_density_matrix(dm)["results"] for key, value in probs_1.items(): assert probs_2[key] == pytest.approx(value) for key, value in probs_2.items(): assert probs_1[key] == pytest.approx(value) ================================================ FILE: tests/simulators/test_simulator_factory.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.simulators import SimulatorFactory, Simulator, DelaySimulator, LossSimulator, PolarizationSimulator from perceval.components import BS, PBS, Unitary, PS, TD, LC, Processor from perceval.backends._slos import SLOSBackend from perceval.backends._naive import NaiveBackend from perceval.utils import BasicState import numpy as np def test_create_simulator_from_circuit(): c = BS() simu = SimulatorFactory.build(c) assert isinstance(simu, Simulator) assert isinstance(simu._backend, SLOSBackend) # Default backend is SLOS assert simu._backend._circuit == c simu = SimulatorFactory.build(c, SLOSBackend()) assert isinstance(simu, Simulator) assert isinstance(simu._backend, SLOSBackend) simu = SimulatorFactory.build(c, "SLOS") assert isinstance(simu, Simulator) assert isinstance(simu._backend, SLOSBackend) simu = SimulatorFactory.build(c, "Naive") assert isinstance(simu, Simulator) assert isinstance(simu._backend, NaiveBackend) def test_create_simulator_from_polarized_circuit(): c2 = PBS() simu = SimulatorFactory.build(c2) assert isinstance(simu, PolarizationSimulator) assert isinstance(simu._simulator, Simulator) assert isinstance(simu._simulator._backend, SLOSBackend) # PolarizationSimulator prepares _upol and the circuit is set only when a polarized input is set assert np.allclose(simu._upol, c2.compute_unitary()) simu.probs(BasicState('|{P:H},0>')) assert isinstance(simu._simulator._backend._circuit, Unitary) # The resulting circuit is set as a Unitary def test_create_simulator_from_components(): cp_list_1 = [((0,1), BS()), ((1,), PS(phi=2)), ((0,1), BS())] simu = SimulatorFactory.build(cp_list_1, "SLOS") assert isinstance(simu, Simulator) assert isinstance(simu._backend, SLOSBackend) cp_list_2 = [((0,1), BS()), ((1,), TD(dt=2)), ((0,1), BS())] simu = SimulatorFactory.build(cp_list_2, "Naive") assert isinstance(simu, DelaySimulator) assert isinstance(simu._simulator, Simulator) assert isinstance(simu._simulator._backend, NaiveBackend) cp_list_3 = [((0,1), BS()), ((1,), LC(loss=0.2)), ((0,1), BS())] simu = SimulatorFactory.build(cp_list_3) assert isinstance(simu, LossSimulator) assert isinstance(simu._simulator, Simulator) assert isinstance(simu._simulator._backend, SLOSBackend) def test_create_simulator_from_complex_processor(): p = Processor("SLOS", 2) p.add(0, BS()) p.add(0, TD(dt=1)) # Triggers the use of DelaySimulator p.add(0, PS(phi=0.5)) p.add(1, LC(loss=0.1)) # Triggers the use of LossSimulator p.add(0, PBS()) # Triggers the use of PolarizationSimulator simu = SimulatorFactory.build(p) assert isinstance(simu, LossSimulator) assert isinstance(simu._simulator, DelaySimulator) assert isinstance(simu._simulator._simulator, PolarizationSimulator) assert isinstance(simu._simulator._simulator._simulator, Simulator) ================================================ FILE: tests/simulators/test_simulator_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from .._test_utils import assert_sv_close from perceval.utils import BSDistribution, FockState, StateVector from perceval.simulators._simulator_utils import _merge_sv, _list_merge from math import sqrt def test_merge_sv(): a1 = complex(0, 1/sqrt(2)) b1 = complex(-1.2, 3.7) sv1 = a1 * StateVector([1, 0]) + b1 * StateVector([0, 1]) a2 = complex(-0.598, -0.65) b2 = complex(0.15, 0.297) sv2 = a2 * StateVector([1, 0]) + b2 * StateVector([0, 1]) sv_res = _merge_sv(sv1, sv2) assert_sv_close( sv_res, a1*a2 * StateVector([2, 0]) + (a1*b2 + b1*a2) * StateVector([1, 1]) + b1*b2 * StateVector([0, 2]) ) def test_list_merge(): bsd1 = BSDistribution() bsd1.add(FockState([1, 1, 0]), 0.25) bsd1.add(FockState([1, 0, 1]), 0.15) bsd1.add(FockState([0, 1, 1]), 0.05) bsd1.add(FockState([2, 0, 0]), 0.2) bsd1.add(FockState([0, 2, 0]), 0.34999) bsd1.add(FockState([0, 0, 2]), 0.00001) bsd2 = BSDistribution() bsd2.add(FockState([1, 0, 0]), 0.2) bsd2.add(FockState([0, 0, 1]), 0.3) bsd2.add(FockState([0, 1, 0]), 0.5) input_lists = ( [(s, p) for s, p in bsd1.items()], [(s, p) for s, p in bsd2.items()], ) expected_res = BSDistribution.list_tensor_product([bsd1, bsd2], merge_modes=True) expected_dict = {s: p for s, p in expected_res.items()} res = _list_merge(input_lists) assert expected_dict == pytest.approx(res) # Change the order or input_lists input_lists = ( [(s, p) for s, p in bsd2.items()], [(s, p) for s, p in bsd1.items()], ) res = _list_merge(input_lists) assert expected_dict == pytest.approx(res) len_without_threshold = len(res) # With a probability threshold prob_threshold = 1e-3 expected_res = BSDistribution.list_tensor_product([bsd1, bsd2], merge_modes=True, prob_threshold=prob_threshold) expected_dict = {s: p for s, p in expected_res.items()} res = _list_merge(input_lists, prob_threshold) assert expected_dict == pytest.approx(res) assert len(res) < len_without_threshold ================================================ FILE: tests/simulators/test_stepper.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest from perceval.components import BS, PS, GenericInterferometer from perceval.simulators import Stepper, Simulator from perceval.utils import BasicState from perceval.backends import NaiveBackend def test_stepper_basic_interference(): c = BS() sim = Stepper() sim.set_circuit(c) res = sim.probs(BasicState([1, 1])) assert len(res) == 2 assert pytest.approx(res[BasicState([2, 0])]) == 0.5 assert pytest.approx(res[BasicState([0, 2])]) == 0.5 def test_stepper_complex_circuit(): def _gen_mzi(i: int): return BS(BS.r_to_theta(0.42)) // PS(math.pi+i*0.1) // BS(BS.r_to_theta(0.52)) // PS(math.pi/2) c = GenericInterferometer(4, _gen_mzi) stepper_sim = Stepper() stepper_sim.set_circuit(c) stepper_res = stepper_sim.probs(BasicState([1, 0, 1, 0])) slos_sim = Simulator(NaiveBackend()) slos_sim.set_circuit(c) slos_res = slos_sim.probs(BasicState([1, 0, 1, 0])) assert len(stepper_res) == len(slos_res) for stepper_bs, stepper_p in stepper_res.items(): assert slos_res[stepper_bs] == pytest.approx(stepper_p) ================================================ FILE: tests/test_test_utils.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval.utils import StateVector from ._test_utils import assert_sv_close def test_utils(): sv_0_1 = StateVector([0, 1]) sv_1_0 = StateVector([1, 0]) sv_1_1 = StateVector([1, 1]) sv1 = sv_0_1 + sv_1_0 sv1_bis = 1.0000001*sv_0_1 + 0.9999999*sv_1_0 sv2 = sv_0_1 - sv_1_0 sv3 = sv_0_1 + sv_1_1 sv4 = sv_0_1 assert_sv_close(sv1, sv1_bis) with pytest.raises(AssertionError): assert_sv_close(sv1, sv2) with pytest.raises(AssertionError): assert_sv_close(sv1, sv3) with pytest.raises(AssertionError): assert_sv_close(sv1, sv4) with pytest.raises(AssertionError): assert_sv_close(sv4, sv1) ================================================ FILE: tests/utils/__init__.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. ================================================ FILE: tests/utils/test_circuit_optimizer.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from collections.abc import Callable from perceval.utils.algorithms.circuit_optimizer import CircuitOptimizer from perceval.utils.algorithms import norm from perceval.components import BS, PS, Circuit, GenericInterferometer from perceval.utils import P, Matrix import pytest perfect_theta = BS.r_to_theta(r=.5) def _ps(i): return PS(P(f"phi_3_{i}")) def _check_optimize(size: int, mzi_func: Callable[[int], None], max_eval_per_trial_tested: int = None): if max_eval_per_trial_tested is None: circuit_optimizer = CircuitOptimizer() else: circuit_optimizer = CircuitOptimizer(max_eval_per_trial=max_eval_per_trial_tested) template_interferometer = GenericInterferometer(size, mzi_func, phase_shifter_fun_gen=_ps, phase_at_output=True) random_unitary = Matrix.random_unitary(size) result_circuit, fidelity = circuit_optimizer.optimize(random_unitary, template_interferometer) assert 1 - fidelity < circuit_optimizer.threshold assert norm.fidelity(result_circuit.compute_unitary(), random_unitary) == pytest.approx(fidelity) @pytest.mark.long_test def test_circuit_optimizer(): def mzi(i): return Circuit(2) // PS(P(f"phi_1_{i}")) // BS.Rx(perfect_theta) \ // PS(P(f"phi_2_{i}")) // BS.Rx(perfect_theta) for size in range(6, 17, 2): _check_optimize(size, mzi) @pytest.mark.long_test def test_circuit_optimizer_bs_convention(): for bs_ctor in [BS.Ry, BS.H]: def mzi_conv(i): return Circuit(2) // PS(P(f"phi_1_{i}")) // bs_ctor(perfect_theta) \ // PS(P(f"phi_2_{i}")) // bs_ctor(perfect_theta) _check_optimize(12, mzi_conv) @pytest.mark.parametrize("nb_iteration, expected_success", [(None, True), (20, False), (50000, True)]) def test_circuit_optimizer_max_eval_convergence(nb_iteration: int, expected_success: bool): def mzi(i): return Circuit(2) // PS(P(f"phi_1_{i}")) // BS.Rx(perfect_theta) \ // PS(P(f"phi_2_{i}")) // BS.Rx(perfect_theta) success = False try: _check_optimize(10, mzi, nb_iteration) success = True except: pass assert success == expected_success ================================================ FILE: tests/utils/test_density_matrix.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest import numpy as np from perceval import BasicState, Source, SVDistribution, Matrix, Simulator, \ Unitary, Circuit, SLOSBackend from perceval.utils.density_matrix import FockBasis, DensityMatrix from perceval.utils.density_matrix_utils import * def test_fock_basis(): basis = FockBasis(12,3) assert basis.m == 12 assert len(basis) == 455 basis.add_photon() assert len(basis) == 1820 basis.add_photons(2) assert len(basis) == 18564 assert basis.n_max == 6 def test_statevector_to_array(): index = FockBasis(2, 2) sv = StateVector(BasicState([1, 1])) vector = np.zeros(6, dtype=complex) vector[4] = 1 assert statevector_to_array(sv, index) == pytest.approx(vector) def test_create_index(): dic = FockBasis(10, 5) assert max([basic_state.n for basic_state in dic]) == 5 for basic_state in dic: assert basic_state.m == 10 def test_density_matrix(): sv = BasicState([0]) + BasicState([1]) dm = DensityMatrix.from_svd(sv) dm2 = DensityMatrix.from_svd(BasicState([0])) dm3 = dm * 2 dm4 = 2 * dm assert (dm + dm2).size == 2 assert (dm + dm).size == 2 test_mat = np.ones((2,2)) assert np.allclose(dm.mat.toarray(), 0.5*test_mat, 1e-6, 1e-6) assert np.allclose(dm3.mat.toarray(), test_mat, 1e-6, 1e-6) assert np.allclose(dm4.mat.toarray(), test_mat, 1e-6, 1e-6) tensor_dm_1 = dm * dm tensor_dm_2 = DensityMatrix.from_svd(sv * sv) tensor_dm_3 = dm*sv assert tensor_dm_1.shape == (6, 6) assert tensor_dm_2.mat.trace() == pytest.approx(1) for i in range(tensor_dm_2.size): for j in range(tensor_dm_2.size): assert tensor_dm_1.mat[i, j] == pytest.approx(tensor_dm_2.mat[i, j]) assert tensor_dm_1.mat[i, j] == pytest.approx(tensor_dm_3.mat[i, j]) assert dm[BasicState([0]), BasicState([1])] == pytest.approx(.5) assert (dm+dm2).size == 2 assert (dm+dm).size == 2 def test_density_matrix_to_svd(): source = Source(.9) svd1 = source.generate_distribution(BasicState([0, 1, 0, 1])) svd2 = source.generate_distribution(BasicState([0, 1])) tensor_svd = svd1*svd2 dm1 = DensityMatrix.from_svd(svd1) dm2 = DensityMatrix.from_svd(svd2) svd1_back = dm1.to_svd() tensor_svd_back = (dm1*dm2).to_svd() assert len(svd1_back) == len(svd1) assert len(tensor_svd_back) == len(tensor_svd) def test_density_matrix_array_constructor(): matrix = np.array([[0.8, 0], [0, 0.2]]) index = FockBasis(1, 1) dm1 = DensityMatrix(matrix, index) dm2 = DensityMatrix.from_svd(SVDistribution({BasicState([0]): .8, BasicState([1]): .2})) dm3 = DensityMatrix(matrix, m=1, n_max=1) assert np.allclose(dm1.mat.toarray(), dm3.mat.toarray()) assert np.allclose(dm1.mat.toarray(), dm2.mat.toarray()) def test_sample(): dm = DensityMatrix.from_svd(BasicState([1])) for x in dm.sample(10): assert x == BasicState([1]) def test_avoid_annotations(): with pytest.raises(ValueError): DensityMatrix.from_svd(BasicState('|{_:1}>')+BasicState('|{_:2}>')) def test_remove_low_amplitude(): s = Source(.991) dm = DensityMatrix.from_svd(s.generate_distribution(BasicState([1, 0, 1, 0, 1, 0]))) assert dm.mat.nnz == 8 assert dm.mat.trace() == pytest.approx(1) dm.remove_low_amplitude() assert dm.mat.nnz == 7 assert dm.mat.trace() == pytest.approx(1) assert dm[BasicState([0, 0, 0, 0, 0, 0]), BasicState([0, 0, 0, 0, 0, 0])] == 0 dm.remove_low_amplitude(1e-3) assert dm.mat.nnz == 4 assert dm.mat.trace() == pytest.approx(1) dm.remove_low_amplitude(1e-1) assert dm.mat.nnz == 1 assert dm.mat.trace() == pytest.approx(1) def test_divide_fockstate(): fs = BasicState([2, 0, 0, 1, 4]) meas, remain = DensityMatrix._divide_fock_state(fs, [0, 2, 4]) assert meas == BasicState([2, 0, 4]) assert remain == BasicState([0, 1]) def test_measure_density_matrix(): plus_state = BasicState([0]) + BasicState([1]) minus_state = BasicState([0]) - BasicState([1]) svd = SVDistribution({StateVector(BasicState([0]))*plus_state: 1/3, StateVector(BasicState([1]))*minus_state: 2/3}) dm = DensityMatrix.from_svd(svd) dic = dm.measure([0]) p0, sub_dm_0 = dic[BasicState([0])] p1, sub_dm_1 = dic[BasicState([1])] assert p0 == pytest.approx(1/3) assert p1 == pytest.approx(2/3) assert sub_dm_0.mat.toarray() == pytest.approx(1/2*np.array([[1, 1, 0], [1, 1, 0], [0, 0, 0]])) assert sub_dm_1.mat.toarray() == pytest.approx(1/2*np.array([[1, -1], [-1, 1]])) sv = BasicState([1, 1, 1, 1]) + BasicState([2, 0, 2, 0]) + BasicState([2, 0, 1, 1]) equivalent_dm = DensityMatrix.from_svd(sv) measurements = sv.measure([0, 1]) measurements_dm = equivalent_dm.measure([0, 1]) assert len(measurements) == len(measurements_dm) for state in measurements.keys(): p_sv, rstate = measurements[state] p_dm, rdm = measurements_dm[state] assert p_sv == pytest.approx(p_dm) dm_comparison = DensityMatrix.from_svd(rstate, index=rdm.index) assert dm_comparison.mat.toarray() == pytest.approx(rdm.mat.toarray()) def test_photon_loss(): dm = DensityMatrix.from_svd(BasicState([5])) dm.apply_loss(0, .5) assert dm.mat.trace() == pytest.approx(1) for k in range(6): assert dm[BasicState([k]), BasicState([k])] == pytest.approx(math.comb(5, k) * (1/2)**5) sv = BasicState([2, 1, 0]) + BasicState([1, 3, 2]) + BasicState([0, 0, 1]) dm = DensityMatrix.from_svd(sv) dm.apply_loss([0, 1], .2) assert dm.mat.trace() == pytest.approx(1) dm_test = DensityMatrix.from_svd(Source(.5).generate_distribution(BasicState([0, 1, 1]))) dm_to_test = DensityMatrix.from_svd(Source(.5).generate_distribution(BasicState([1, 1]))) # Beam splitter that has a chance 0.75 to make the photon switch its mode (model of a .75 loss probability) virtual_circuit = Circuit(3) // (0, Unitary(Matrix([[.5, .866025404], [-.866025404, .5]]))) sim = Simulator(SLOSBackend()) sim.set_circuit(virtual_circuit) out_svd = sim.evolve_density_matrix(dm_test) remaining_dms = out_svd.measure(0) remaining_dm = sum([prob*dm for (prob, dm) in remaining_dms.values()]) assert remaining_dm.n_max == 2 assert remaining_dm.m == 2 dm_to_test.apply_loss(0, .75) assert dm_to_test.n_max == 2 assert dm_to_test.m == 2 assert remaining_dm.size == dm_to_test.size assert remaining_dm.mat.toarray() == pytest.approx(dm_to_test.mat.toarray()) ================================================ FILE: tests/utils/test_dist_metrics.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from unittest.mock import patch from perceval.utils.dist_metrics import tvd_dist, kl_divergence from perceval.utils import BSDistribution, BasicState import perceval as pcvl from copy import copy from .._test_utils import LogChecker bs1 = BasicState([0, 1, 0]) bs2 = BasicState([0, 0, 0]) bs3 = BasicState([0, 0, 1]) bs4 = BasicState([1, 0, 0]) def test_tvd_identical_dist(): target_bsd = BSDistribution() target_bsd[bs1] = 0.5 target_bsd[bs2] = 0.5 bsd_to_comp = copy(target_bsd) assert tvd_dist(target_bsd, bsd_to_comp) == 0 @patch.object(pcvl.utils.logging.ExqaliburLogger, "warn") def test_tvd_disjoint_dist(mock_warn): target_bsd = BSDistribution() target_bsd[bs1] = 0.5 target_bsd[bs2] = 0.5 bsd_to_comp = BSDistribution() bsd_to_comp[bs3] = 1 bsd_to_comp[bs4] = 0 with LogChecker(mock_warn): assert tvd_dist(target_bsd, bsd_to_comp) == 1.0 @patch.object(pcvl.utils.logging.ExqaliburLogger, "warn") def test_tvd_one_empty_dist(mock_warn): target_bsd = BSDistribution() target_bsd[bs1] = 0.3 target_bsd[bs2] = 0.7 with LogChecker(mock_warn): assert tvd_dist(target_bsd, BSDistribution()) == 0.5 def test_kl_div_identical_dist(): target_bsd = BSDistribution() target_bsd[bs1] = 0.5 target_bsd[bs2] = 0.5 bsd_to_comp = copy(target_bsd) assert kl_divergence(target_bsd, bsd_to_comp) == 0 def test_kl_div_unequal_dist(): ideal_bsd = BSDistribution() # Binomial ideal_bsd[bs1] = 9/25 ideal_bsd[bs2] = 12/25 ideal_bsd[bs3] = 4/25 model_bsd = BSDistribution() # uniform model_bsd[bs1] = 1/3 model_bsd[bs2] = 1/3 model_bsd[bs3] = 1/3 assert kl_divergence(ideal_bsd, model_bsd) != kl_divergence(model_bsd, ideal_bsd) # it is not symmetric model2_bsd = BSDistribution() # Binomial model2_bsd[bs1] = 8/25 model2_bsd[bs2] = 10/25 model2_bsd[bs3] = 7/25 assert kl_divergence(ideal_bsd, model_bsd) > kl_divergence(ideal_bsd, model2_bsd) # Model 2 closer to the ideal ================================================ FILE: tests/utils/test_doc_config.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.utils.versions.version_utils import keep_latest_versions def test_version_filtering(): # All different minor: all should be kept versions = [ "v0.1.0", "v0.2.0", "v0.3.1", ] filtered = keep_latest_versions(versions) assert filtered == versions # Same minor: Only the highest patch should be kept versions = [ "v0.1.0", "v0.2.0", "v0.2.1", ] filtered = keep_latest_versions(versions) assert filtered == ["v0.1.0", "v0.2.1"] # Test that we drop alphas and betas versions = [ "v0.1.0", "v0.2.0", "v0.2.1-beta1", "v0.3.0", "v0.3.1-alpha2", ] filtered = keep_latest_versions(versions) assert filtered == ["v0.1.0", "v0.2.0", "v0.3.0"] # Test the optional minimum version versions = [ "v0.1.0", "v0.2.0", "v0.2.1", "v0.3.0", ] filtered = keep_latest_versions(versions, mini="v0.2.1") assert filtered == ["v0.2.1", "v0.3.0"] # Test mixing major and minor versions = [ "v0.1.0", "v0.2.0", "v0.2.1", "v1.0.0", "v1.0.1", "v1.1.0", ] filtered = keep_latest_versions(versions) assert filtered == ["v0.1.0", "v0.2.1", "v1.0.1", "v1.1.0"] # Test with "randomly" ordered versions (same list than previous test) versions = [ "v1.1.0", "v0.1.0", "v0.2.1", "v1.0.0", "v0.2.0", "v1.0.1", ] filtered = keep_latest_versions(versions) assert filtered == ["v0.1.0", "v0.2.1", "v1.0.1", "v1.1.0"] ================================================ FILE: tests/utils/test_fidelity.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import numpy as np import perceval as pcvl import perceval.utils.algorithms.norm as norm def test_skip_colums_from_frobenius_product(): a = np.array([[1,2,3],[1,2,1]]) assert norm.frobenius_inner_product(a, a) == 20 # sum of squared elements assert norm.frobenius_inner_product(a, a, [1]) == 12 assert norm.frobenius_inner_product(a, a, [1, 2, 2, 2, 51]) == 2 def test_fidelity(): size = 8 # fidelity of a matrix with itself is maximum (1) random_unitary = pcvl.Matrix.random_unitary(size) assert norm.fidelity(random_unitary, random_unitary) == pytest.approx(1) assert norm.fidelity(random_unitary, random_unitary, [ 1, 4, 6 ]) == pytest.approx(1) # fidelity is commutative random_unitary_2 = pcvl.Matrix.random_unitary(size) assert norm.fidelity(random_unitary, random_unitary_2) == \ pytest.approx(norm.fidelity(random_unitary_2, random_unitary)) # fidelity of orthognonal matrices is minimum (0) identity = pcvl.Matrix(np.identity(size)) flipped = pcvl.Matrix(np.flipud(identity)) assert norm.fidelity(identity, flipped) == pytest.approx(0) # fidelity of any matrix with zeros is 0 zero_mat = pcvl.Matrix(np.zeros((size, size))) assert norm.fidelity(random_unitary, zero_mat) == pytest.approx(0) # fidelity can be computed only for same size matrices mat_too_small = pcvl.Matrix.random_unitary(size // 2) with pytest.raises(ValueError): norm.fidelity(random_unitary, mat_too_small) ================================================ FILE: tests/utils/test_format.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import perceval as pcvl import sympy as sp def test_format_simple(): assert pcvl.simple_float(1)[1] == "1" assert pcvl.simple_float(-0)[1] == "0" assert pcvl.simple_float(0.0000000000001)[1] != "1e-10" assert pcvl.simple_float(1.0000000000001)[1] == "1" assert pcvl.simple_float(-2/3)[1] == "-2/3" assert pcvl.simple_float(-2/3, nsimplify=False)[1] == "-0.666667" assert pcvl.simple_float(-2/3, nsimplify=False, precision=1e-7)[1] == "-0.6666667" assert pcvl.simple_float(-2/30000, nsimplify=False, precision=1e-7)[1] == "-6.6666667e-5" assert pcvl.simple_float(float(-23*sp.pi/19))[1] == "-23*pi/19" def test_format_complex(): assert pcvl.simple_complex(1)[1] == "1" assert pcvl.simple_complex(-0)[1] == "0" assert pcvl.simple_complex(0.0000000000001)[1] != "1e-10" assert pcvl.simple_complex(1.0000000000001)[1] == "1" assert pcvl.simple_complex(-2j/3)[1] == "-2*I/3" assert pcvl.simple_complex(complex(1/sp.sqrt(2)-5j*sp.sqrt(5)/3))[1] == "sqrt(2)/2-5*sqrt(5)*I/3" assert pcvl.simple_complex(0.001+1e-15j)[1] == "0.001" assert pcvl.simple_complex(0.0001+1e-15j)[1] == "1e-4" def test_format_pdisplay(capfd): pcvl.pdisplay(0.50000000001) out, err = capfd.readouterr() assert out.strip() == "1/2" pcvl.pdisplay(0.5001) out, err = capfd.readouterr() assert out.strip() == "0.5001" pcvl.pdisplay(0.5001, precision=1e-3) out, err = capfd.readouterr() assert out.strip() == "1/2" pcvl.pdisplay(1j) out, err = capfd.readouterr() assert out.strip() == "I" ================================================ FILE: tests/utils/test_log.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import json from unittest.mock import patch import perceval as pcvl from perceval.utils import LoggerConfig from perceval.utils.logging import ExqaliburLogger, PythonLogger, level, channel from tests.runtime._mock_rpc_handler import get_rpc_handler_for_tests DEFAULT_CONFIG = {'use_python_logger': False, 'enable_file': False, 'channels': {'general': {'level': 'off'}, 'resources': {'level': 'off'}, 'user': {'level': 'warn'}}} @patch.object(ExqaliburLogger, "apply_config") def test_logger_config(mock_apply_config, tmp_path, monkeypatch): monkeypatch.setenv('PCVL_PERSISTENT_PATH', str(tmp_path)) logger_config = LoggerConfig() logger_config.reset() logger_config.save() assert dict(logger_config) == DEFAULT_CONFIG config = logger_config._persistent_data.load_config() assert config["logging"] == DEFAULT_CONFIG pcvl.get_logger().apply_config(logger_config) assert dict(mock_apply_config.call_args[0][0]) == DEFAULT_CONFIG logger_config.enable_file() logger_config.set_level(level.warn, channel.general) logger_config.set_level(level.warn, channel.resources) logger_config.set_level(level.warn, channel.user) logger_config.save() config = logger_config._persistent_data.load_config() new_dict_config = {'use_python_logger': False, 'enable_file': True, 'channels': {'general': {'level': 'warn'}, 'resources': {'level': 'warn'}, 'user': {'level': 'warn'}}} assert config["logging"] == new_dict_config pcvl.get_logger().apply_config(logger_config) assert dict(mock_apply_config.call_args[0][0]) == new_dict_config logger_config.reset() logger_config.save() config = logger_config._persistent_data.load_config() assert config["logging"] == DEFAULT_CONFIG def test_change_logger(): pcvl.use_perceval_logger() assert isinstance(pcvl.get_logger(), ExqaliburLogger) pcvl.use_python_logger() assert isinstance(pcvl.get_logger(), PythonLogger) pcvl.use_perceval_logger() assert isinstance(pcvl.get_logger(), ExqaliburLogger) # This test NEEDS to finish on a use_perceval_logger for all other mocked tests to work def _get_last_dict_logged(mock_info_args): return json.loads(mock_info_args) SOURCE = 'source' NOISE = 'noise' LAYER = 'layer' N = 'n' M = 'm' BACKEND = 'backend' METHOD = 'method' @patch.object(ExqaliburLogger, "info") def test_log_resources(mock_info): pcvl.utils.logging._logger.set_level(level.info, channel.resources) # prepare test parameters input_state = pcvl.BasicState("|1,1,0,0>") circuit = pcvl.Circuit(4) noise_model = pcvl.NoiseModel(brightness=0.2, indistinguishability=0.75, g2=0.05) max_samples = 500 min_detected_photons_filter = 2 proc_slos = pcvl.Processor('SLOS', circuit, noise_model) proc_slos.min_detected_photons_filter(min_detected_photons_filter) proc_slos.with_input(input_state) proc_slos.probs() my_dict = _get_last_dict_logged(mock_info.mock_calls[-1].args[0]) assert SOURCE not in my_dict assert my_dict[NOISE] == noise_model.__dict__() assert my_dict[LAYER] == 'Processor' assert my_dict[BACKEND] == 'SLOS' assert my_dict[N] == input_state.n assert my_dict[M] == circuit.m assert my_dict[METHOD] == 'probs' remote_processor = pcvl.RemoteProcessor.from_local_processor( proc_slos, rpc_handler=get_rpc_handler_for_tests() ) remote_processor.with_input(input_state) remote_processor.prepare_job_payload('probs') my_dict = _get_last_dict_logged(mock_info.mock_calls[-1].args[0]) assert SOURCE not in my_dict assert my_dict['platform'] == remote_processor._rpc_handler.name assert my_dict[NOISE] == noise_model.__dict__() assert my_dict[LAYER] == 'RemoteProcessor' assert my_dict[N] == input_state.n assert my_dict[M] == circuit.m assert my_dict['command'] == 'probs' proc_clicli = pcvl.Processor('CliffordClifford2017', pcvl.Circuit(4), noise=noise_model) proc_clicli.min_detected_photons_filter(min_detected_photons_filter) proc_clicli.with_input(input_state) proc_clicli.samples(max_samples) # TODO: complete the test ? @patch.object(ExqaliburLogger, "info") def test_log_resources_simulator(mock_info): pcvl.get_logger().set_level(level.info, channel.resources) # prepare test parameters input_state = pcvl.BasicState("|1,1,0,0>") circuit = pcvl.Circuit(4) noise_model = pcvl.NoiseModel(brightness=0.2, indistinguishability=0.75, g2=0.05) source = pcvl.Source.from_noise_model(noise_model) input_state_svd = source.generate_distribution(input_state) min_detected_photons_filter = 2 # Simulator sim = pcvl.Simulator(pcvl.SLOSBackend()) sim.set_selection(min_detected_photons_filter=min_detected_photons_filter) sim.set_circuit(circuit) sim.evolve(input_state) my_dict = _get_last_dict_logged(mock_info.mock_calls[-1].args[0]) assert SOURCE not in my_dict assert NOISE not in my_dict assert my_dict[LAYER] == 'Simulator' assert my_dict[BACKEND] == 'SLOS' assert my_dict[N] == input_state.n assert my_dict[M] == circuit.m assert my_dict[METHOD] == 'evolve' sim.probs_svd(input_state_svd) my_dict = _get_last_dict_logged(mock_info.mock_calls[-1].args[0]) assert SOURCE not in my_dict assert NOISE not in my_dict assert my_dict[LAYER] == 'Simulator' assert my_dict[BACKEND] == 'SLOS' assert my_dict[N] == input_state_svd.n_max assert my_dict[M] == circuit.m assert my_dict[METHOD] == 'probs_svd' @patch.object(ExqaliburLogger, "info") def test_log_resources_noisy_sampling_simulator(mock_info): pcvl.get_logger().set_level(level.info, channel.resources) # prepare test parameters input_state = pcvl.BasicState("|1,1,0,0>") circuit = pcvl.Circuit(4) noise_model = pcvl.NoiseModel(brightness=0.2, indistinguishability=0.75, g2=0.05) source = pcvl.Source.from_noise_model(noise_model) input_state_svd = source.generate_distribution(input_state) max_samples = 500 min_detected_photons_filter = 2 # Noisy Simulator Simulator sim = pcvl.simulators.NoisySamplingSimulator(pcvl.Clifford2017Backend()) sim.set_selection(min_detected_photons_filter=min_detected_photons_filter) sim.set_circuit(circuit) sim.samples(input_state_svd, max_samples) my_dict = _get_last_dict_logged(mock_info.mock_calls[-1].args[0]) assert SOURCE not in my_dict assert NOISE not in my_dict assert my_dict[LAYER] == 'NoisySamplingSimulator' assert my_dict[BACKEND] == 'CliffordClifford2017' assert my_dict[N] == input_state_svd.n_max assert my_dict[M] == circuit.m assert my_dict[METHOD] == 'samples' assert my_dict['max_samples'] == max_samples ================================================ FILE: tests/utils/test_logical_state.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval.utils import LogicalState, generate_all_logical_states def test_logical_state(): with pytest.raises(ValueError): LogicalState([0,2,1]) with pytest.raises(ValueError): LogicalState("a01") ls = LogicalState("010011") assert ls == LogicalState([0,1,0,0,1,1]) ls = LogicalState([0,1,0,0,0,1]) assert str(ls) == "|010001>" ls1 = LogicalState([1,0]) assert ls + ls1 == LogicalState([0,1,0,0,0,1,1,0]) def test_generate_all_logical_states(): states = [LogicalState([0,0,0]), LogicalState([0,0,1]), LogicalState([0,1,0]), LogicalState([0,1,1]), LogicalState([1,0,0]), LogicalState([1,0,1]), LogicalState([1,1,0]), LogicalState([1,1,1]),] assert generate_all_logical_states(3) == states states = [LogicalState([0,0,0,0]), LogicalState([0,0,0,1]), LogicalState([0,0,1,0]), LogicalState([0,0,1,1]), LogicalState([0,1,0,0]), LogicalState([0,1,0,1]), LogicalState([0,1,1,0]), LogicalState([0,1,1,1]), LogicalState([1,0,0,0]), LogicalState([1,0,0,1]), LogicalState([1,0,1,0]), LogicalState([1,0,1,1]), LogicalState([1,1,0,0]), LogicalState([1,1,0,1]), LogicalState([1,1,1,0]), LogicalState([1,1,1,1]),] assert generate_all_logical_states(4) == states ================================================ FILE: tests/utils/test_mask.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import exqalibur as xq def test_mask(): mask = xq.FSMask(6, 4, [" 11"]) assert mask.match(xq.FockState("|0,0,1,1,1,1>")) # no partial masking (we don't accept lower number of photons) assert not mask.match(xq.FockState("|0,0,1,1,1,0>"), False) # partial masking (we accept lower number of photons) assert mask.match(xq.FockState("|0,0,1,1,1,0>"), True) def test_mask_multiple(): mask = xq.FSMask(6, 4, [" 011", " 110"]) assert mask.match(xq.FockState("|0,0,1,0,1,1>")) assert mask.match(xq.FockState("|0,0,1,1,1,0>")) assert not mask.match(xq.FockState("|0,0,1,1,1,1>")) ================================================ FILE: tests/utils/test_matrix.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from pathlib import Path import numpy as np import sympy as sp import perceval as pcvl from perceval.rendering.pdisplay import pdisplay_matrix TEST_DATA_DIR = Path(__file__).resolve().parent.parent / 'data' def test_new_np(): u = sp.eye(3) M = pcvl.Matrix(u) assert u.equals(M) assert not M.is_symbolic() def test_new_textarray(): M = pcvl.Matrix("1 2 3\n4 5 6\n7 8 9") assert np.array_equal(M, np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) assert not M.is_symbolic() def test_new_shape0(): M = pcvl.Matrix((3,)) assert M.shape == (3, 1) def test_new_shape1(): M = pcvl.Matrix(3) assert M.shape == (3, 3) def test_new_shape2(): M = pcvl.Matrix((1, 3)) assert M.shape == (1, 3) def test_eye_n(): M = pcvl.Matrix.eye(3) assert isinstance(M, np.ndarray) assert M.shape == (3, 3) assert pdisplay_matrix(M) == "⎡1 0 0⎤\n⎢0 1 0⎥\n⎣0 0 1⎦" def test_eye_s(): M = pcvl.Matrix.eye(3, use_symbolic=True) assert pdisplay_matrix(M) == "⎡1 0 0⎤\n⎢0 1 0⎥\n⎣0 0 1⎦" assert isinstance(M, sp.Matrix) assert M.shape == (3, 3) def test_zero_n(): M = pcvl.Matrix.zeros((3, 3)) assert isinstance(M, np.ndarray) assert M.shape == (3, 3) assert pdisplay_matrix(M) == "⎡0 0 0⎤\n⎢0 0 0⎥\n⎣0 0 0⎦" def test_zero_s(): M = pcvl.Matrix.zeros((3, 3), use_symbolic=True) assert pdisplay_matrix(M) == "⎡0 0 0⎤\n⎢0 0 0⎥\n⎣0 0 0⎦" assert isinstance(M, sp.Matrix) assert M.shape == (3, 3) assert M.is_symbolic() def test_read_fromfile1(): with open(TEST_DATA_DIR / 'u_hom', "r") as f: M = pcvl.Matrix(f) assert M.shape == (2, 2) def test_read_fromfile_complex(): with open(TEST_DATA_DIR / 'u_random_8', "r") as f: M = pcvl.Matrix(f) assert M.shape == (8, 8) assert float(abs((M[0, 0] - (-0.3233639242889934+0.10358205117585266*sp.I))**2)) < 1e-10 assert M.is_unitary() def test_check_unitary_numeric(): with open(TEST_DATA_DIR / 'u_hom', "r") as f: M = pcvl.Matrix(f) assert M.is_unitary() def test_check_unitary_numeric_sym(): with open(TEST_DATA_DIR / 'u_hom_sym', "r", encoding="utf-8") as f: M = pcvl.Matrix(f) assert M.is_unitary() def atest_str_1(): M = pcvl.Matrix([1, "2*x", 3]) assert M.shape == (3, 1) assert str(M) == "Matrix([[1], [2*x], [3]])" def atest_repr(): M = pcvl.Matrix([[1, "sqrt(2)"], ["-cos(x)", "-1"]]) assert sp.pretty(M) == "⎡ 1 √2⎤\n⎢ ⎥\n⎣-cos(x) -1⎦" def atest_repr_1(): M = pcvl.Matrix([1, "2*x", 3]) assert sp.pretty(M) == "⎡ 1 ⎤\n⎢ ⎥\n⎢2⋅x⎥\n⎢ ⎥\n⎣ 3 ⎦" def atest_repr_2(): M = pcvl.Matrix([[1, "2*x", 3]]) assert pdisplay_matrix(M) == "[1 2*x 3]" def test_repr_3(): M = pcvl.Matrix([[1, 0], [0, 1]]) assert pdisplay_matrix(M) == "⎡1 0⎤\n⎣0 1⎦" def atest_str_2(): M = pcvl.Matrix([[1, "sqrt(2)"], ["-cos(x)", "-1"]]) assert M.shape == (2, 2) assert str(M) == "Matrix([[1, sqrt(2)], [-cos(x), -1]])" def atest_tonumpy(): M = pcvl.Matrix([["1/sqrt(2)", "-1/sqrt(2)"], ["1/sqrt(2)", "1/sqrt(2)"]]) assert np.allclose(M.tonp(), np.array([[0.70710678, -0.70710678], [0.70710678, 0.70710678]])) def test_keeppcvlcls(): M = pcvl.Matrix([3]) N = pcvl.Matrix([4]) assert isinstance(M, pcvl.Matrix) assert isinstance(N, pcvl.Matrix) MN = M*N assert MN == pcvl.Matrix([12]) assert isinstance(MN, pcvl.Matrix) def test_keeppcvlcls_s(): M = pcvl.Matrix([3], use_symbolic=True) N = pcvl.Matrix([4], use_symbolic=True) assert isinstance(M, pcvl.Matrix) assert isinstance(N, pcvl.Matrix) MN = M*N assert MN == pcvl.Matrix([12], use_symbolic=True) assert isinstance(MN, pcvl.Matrix) def test_genunitary(): M = pcvl.Matrix.random_unitary(3) assert isinstance(M, pcvl.Matrix) assert M.shape == (3, 3) assert M.is_unitary() def test_param_unitary(): m = pcvl.Matrix.parametrized_unitary(2, np.arange(8)) assert isinstance(m, pcvl.Matrix) assert m.shape == (2, 2) assert m.is_unitary() # Testing the use of new method - parametrized_unitary() pm = pcvl.Matrix.parametrized_unitary(2, np.arange(8)) assert isinstance(pm, pcvl.Matrix) assert pm.shape == (2, 2) assert pm.is_unitary() ================================================ FILE: tests/utils/test_metadata.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval import PMetadata, __version__ def test_metadata(): assert PMetadata.package_name() == "perceval-quandela" assert PMetadata.author() == "quandela" assert PMetadata.version() == __version__ assert __version__.startswith(PMetadata.short_version()) ================================================ FILE: tests/utils/test_mlstr.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.utils import mlstr import numpy as np def test_basic0(): s = mlstr(0.234) assert str(s) == "0.234" def test_basic1(): s = mlstr() assert str(s) == "" def test_basic2(): s = mlstr("string") assert str(s) == "string" def test_iadd0(): s = mlstr("string") s += "123" assert str(s) == "string123" def test_iadd1(): s = mlstr("M = ") s += "|0 1|\n|1 0|" assert str(s) == "M = |0 1|\n |1 0|" def test_radd(): assert str(1+mlstr("a\nb")) == "1a\n b" def test_iadd_inv(): s = "M = " s + mlstr("|0 1|\n|1 0|") def test_format(): s = mlstr("%s = 1/%f * %s") assert str(s % ("M", np.sqrt(2), "|0 1|\n|1 0|")) == "M = 1/1.414214 * |0 1|\n |1 0|" def test_join(): s = mlstr(" ").join(["a", "a\nb", "c", "a\ng\nc"]) assert str(s) == "a a c a\n b g\n c" ================================================ FILE: tests/utils/test_noise_model.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.utils import NoiseModel import pytest BRIGHTNESS_KEY = "brightness" BRIGHTNESS_DEF = 1 PHASE_IMPRECISION_KEY = "phase_imprecision" G2_KEY = "g2" def test_noise_model_default(): nm = NoiseModel() assert len(nm.__dict__()) == 0 assert nm.brightness == BRIGHTNESS_DEF # Test we can assign a value using setattr nm.g2 = 0.1 assert nm.g2 == 0.1 def test_noise_model_init(): nm = NoiseModel(phase_imprecision=1e-3, g2=0.05) assert PHASE_IMPRECISION_KEY in nm.__dict__() assert G2_KEY in nm.__dict__() assert BRIGHTNESS_KEY not in nm.__dict__() assert nm.brightness == BRIGHTNESS_DEF def test_noise_model_errors(): with pytest.raises(ValueError): NoiseModel(brightness=1.1) with pytest.raises(ValueError): NoiseModel(brightness=-1) with pytest.raises(TypeError): NoiseModel(brightness="bad type") with pytest.raises(ValueError): NoiseModel(g2=1.1) with pytest.raises(ValueError): NoiseModel(g2=-1) with pytest.raises(TypeError): NoiseModel(g2="bad type") with pytest.raises(TypeError): NoiseModel(g2_distinguishable=0.7) # Expects a boolean nm = NoiseModel() with pytest.raises(ValueError): nm.brightness = 1.3 with pytest.raises(TypeError): nm.g2_distinguishable = 1.3 def test_noise_model_eq(): nm1 = NoiseModel(brightness=0.15, indistinguishability=0.89, g2=0.01, g2_distinguishable=False) nm2 = NoiseModel(brightness=0.15, indistinguishability=0.89, g2=0.01, g2_distinguishable=False) assert nm1 == nm2 nm2.brightness = 0.2 assert nm1 != nm2 nm2 = NoiseModel(brightness=0.15, indistinguishability=0.89, g2=0.01) assert nm1 != nm2 nm3 = NoiseModel(brightness=0.01, indistinguishability=0.89, g2=0.15, g2_distinguishable=False) assert nm1 != nm3 ================================================ FILE: tests/utils/test_optimize.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest import perceval as pcvl from perceval.utils.algorithms.optimize import optimize from perceval.utils.algorithms.norm import fidelity, frobenius import perceval.components.unitary_components as comp def _create_circuit(): return (pcvl.Circuit(3, name="rewrite") // (0, comp.PS(pcvl.P("beta2"))) // (1, comp.PS(pcvl.P("beta1"))) // (1, comp.BS.H(theta=pcvl.P("alpha1"))) // (0, comp.BS.H(theta=pcvl.P("alpha2"))) // (1, comp.PS(pcvl.P("beta3"))) // (1, comp.BS.H(theta=pcvl.P("alpha3"))) // (0, comp.PS(pcvl.P("beta4"))) // (1, comp.PS(pcvl.P("beta5"))) // (2, comp.PS(pcvl.P("beta6")))) @pytest.mark.long_test def test_optimize_fidelity(): c = _create_circuit() v = pcvl.Matrix.random_unitary(3) res = optimize(c, v, fidelity) assert pytest.approx(1) == res.fun def test_optimize_frobenius(): c = _create_circuit() v = pcvl.Matrix.random_unitary(3) res = optimize(c, v, frobenius, sign=-1) # test that the frobenius norm is almost 0 (pytest.approx will not work with almost 0) assert pytest.approx(0.5) == res.fun+0.5 ================================================ FILE: tests/utils/test_parameter.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest import sympy as sp from perceval import Parameter, Expression import perceval.components.unitary_components as comp from perceval.rendering.pdisplay import pdisplay_matrix def test_definition(): p = Parameter("alpha", 0) assert isinstance(p.spv, sp.Number) assert p.spv == 0 assert p.defined and float(p) == 0 def test_variable(): p = Parameter("alpha") assert isinstance(p.spv, sp.Expr) assert not p.defined assert p.is_variable def test_set_variable(): p = Parameter("alpha") p.set_value(0.5) assert isinstance(p.spv, sp.Number) assert p.defined assert float(p) == 0.5 def test_fixed_0(): p = Parameter("alpha", 2) assert p.fixed assert p.defined assert not p.is_variable with pytest.raises(RuntimeError): p.set_value(1) # Cannot set value to a fixed parameter def test_fixed_1(): p = Parameter("alpha") assert not p.fixed assert not p.defined assert p.is_variable p.set_value(1) assert not p.fixed assert p.defined def test_basic_conv(): # initially we were trying to convert numeric values into remarkable sympy expression, we are stopping that # due to overhead in sympy p = Parameter("R", 1/3) assert p._value == 1/3 def test_invalid_values(): with pytest.raises(ValueError): Parameter("R", -1, 0, 1, False) with pytest.raises(ValueError): p = Parameter("R", None, 0, 1, False) p.set_value(-1) p = Parameter("R", None, 0, 1) p.set_value(0) def test_periodic_values(): p = Parameter("theta", 0, 0, 2*sp.pi) assert float(p)==0 p = Parameter("theta", 5*math.pi/2, 0, 2 * sp.pi) assert float(p) == float(math.pi/2) def test_multiple_parameter_use(): phi = Parameter("phi") c = comp.BS.H(phi_bl=phi) // comp.BS.H(phi_tl=phi) assert pdisplay_matrix(c.U.simp()) == '''⎡exp(I*phi)/2 + 1/2 (exp(I*phi) - 1)*exp(I*phi)/2⎤ ⎣exp(I*phi)/2 - 1/2 (exp(I*phi) + 1)*exp(I*phi)/2⎦''' def test_expression_arithmetic(): p_a = Parameter("A") p_b = Parameter("B") sum_ab = Expression("A + B", {p_a, p_b}) p_c = Parameter("C") sum_abc = sum_ab + p_c assert "A + B + C" in sum_abc.name a = 5 b = 6 c = 1 p_a.set_value(a) p_b.set_value(b) p_c.set_value(c) assert float(sum_abc) == a + b + c diff_ab = Expression("A - B", {p_a, p_b}) diff_ab_over_c = diff_ab / p_c assert float(diff_ab_over_c) == (a - b) / c p_d = Parameter("D") diff_cd = Expression("C - D", {p_c, p_d}) d = 8 diff_ab_over_diff_cd = diff_ab / diff_cd p_d.set_value(d) new_a = 2 p_a.set_value(new_a) assert float(diff_ab_over_diff_cd) == (new_a - b) / (c - d) def test_expression_missing_parameter(): p_a = Parameter("A") with pytest.raises(RuntimeError): Expression("A + B", {p_a}) def test_expression_math_functions(): p_a = Parameter("A") cos_a = Expression("cos(A)", {p_a}) a = math.pi / 3 p_a.set_value(a) assert float(cos_a) == pytest.approx(math.cos(a)) cos_a_sq = cos_a**2 assert float(cos_a_sq) == pytest.approx(math.cos(a) ** 2) def test_expression_parameter_retrieval(): param_names = ["A", "B", "CCC", "Day", "theta"] params = set() for n in param_names: params.add(Parameter(n)) sum_params = Expression("+".join(param_names), params) # Builds the sum of all params param_list = sum_params.parameters assert len(param_list) == len(param_names) for p in param_list: assert p.name in param_names ================================================ FILE: tests/utils/test_persistent_data.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os import platform import pytest from perceval.utils import PersistentData, FileFormat def test_directory(): persistent_data = PersistentData() assert os.path.isabs(persistent_data.directory) assert persistent_data.directory.endswith("perceval-quandela") def test_basic_methods(tmp_path, monkeypatch): monkeypatch.setenv('PCVL_PERSISTENT_PATH', str(tmp_path)) persistent_data = PersistentData() persistent_data.clear_all_data() assert os.path.exists(persistent_data.directory) assert persistent_data.is_writable() assert persistent_data.is_readable() assert not persistent_data.has_file("toto") persistent_data.write_file("toto", b"", FileFormat.BINARY) assert os.path.exists(os.path.join(persistent_data.directory, "toto")) assert persistent_data.has_file("toto") persistent_data.delete_file("toto") assert not persistent_data.has_file("toto") with pytest.warns(UserWarning): persistent_data.delete_file("toto") with pytest.raises(FileNotFoundError): persistent_data.read_file("toto", FileFormat.BINARY) persistent_data.write_file("toto", b"DEADBEEFDEADBEEF", FileFormat.BINARY) assert persistent_data.read_file("toto", FileFormat.BINARY) == b"DEADBEEFDEADBEEF" assert persistent_data.get_folder_size() == 16 persistent_data.delete_file("toto") assert persistent_data.get_folder_size() == 0 persistent_data.write_file("toto", "DEADBEEFDEADBEEF", FileFormat.TEXT) assert persistent_data.read_file("toto", FileFormat.TEXT) == "DEADBEEFDEADBEEF" assert persistent_data.get_folder_size() == 16 persistent_data.delete_file("toto") assert persistent_data.get_folder_size() == 0 with pytest.raises(TypeError): persistent_data.write_file("toto", "DEADBEEFDEADBEEF", FileFormat.BINARY) persistent_data.delete_file("toto") with pytest.raises(TypeError): persistent_data.write_file("toto", b"DEADBEEFDEADBEEF", FileFormat.TEXT) persistent_data.clear_all_data() @pytest.mark.skipif(platform.system() == "Windows", reason="chmod doesn't works on windows") def test_access(tmp_path, monkeypatch): monkeypatch.setenv('PCVL_PERSISTENT_PATH', str(tmp_path)) # pytest creates tmp_path with access mode depending on temp files management persistent_data = PersistentData() # tmp_path is already created, PersistentData will not create it here # (if tmp_path was not created, it would have been created with mode=0o777 here) directory = persistent_data.directory os.chmod(directory, 0o000) assert not persistent_data.is_writable() assert not persistent_data.is_readable() os.chmod(directory, 0o400) assert not persistent_data.is_writable() assert persistent_data.is_readable() os.chmod(directory, 0o700) assert persistent_data.is_writable() assert persistent_data.is_readable() persistent_data.write_file("test_read_write", "test", FileFormat.TEXT) assert persistent_data.read_file("test_read_write", FileFormat.TEXT) == "test" persistent_data.clear_all_data() ================================================ FILE: tests/utils/test_polarization.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from perceval.utils.polarization import Polarization, convert_polarized_state, build_spatial_output_states from perceval.utils import BasicState from perceval.rendering.pdisplay import pdisplay_matrix from perceval.components import Circuit, BS, PBS, PS, WP import pytest import sympy as sp def test_polar_parse_error(): invalid_str = {"a": "angle value should not contain variable", "(1,2,3)": "more than two parameters", "(1,2": "missing closing parenthesis", "2*pi": "theta should be in [0,pi]"} for s, error in invalid_str.items(): with pytest.raises(ValueError) as parse_error: Polarization.parse(s) assert str(parse_error.value).find(error) != -1, "'%s' - does not match '%s'" % (str(parse_error), error) def test_polar_parse_ok(): valid_str = {"H": (0, 0, "H"), "V": (sp.pi, 0, "V"), "D": (sp.pi/2, 0, "D"), "A": (sp.pi/2, sp.pi, "A"), "R": (sp.pi/2, 3*sp.pi/2, "R"), "L": (sp.pi/2, sp.pi/2, "L"), "0": (0, 0, "H"), "(0,0)": (0, 0, "H"), "pi": (sp.pi, 0, "V"), "(pi,0)": (sp.pi, 0, "V"), "(pi/2,pi)": (sp.pi/2, sp.pi, "A"), "(pi/2,3*pi/2)": (sp.pi / 2, 3*sp.pi/2, "R"), "(pi/2,1/2*pi)": (sp.pi / 2, sp.pi/2, "L"), "(pi/4,0)": (sp.pi/4, 0, "pi/4"), "(pi/4,pi/2)": (sp.pi / 4, sp.pi/2, "(pi/4,pi/2)")} for k, (theta, phi, s) in valid_str.items(): p = Polarization.parse(k) assert pytest.approx(float(theta)) == float(p.theta_phi[0]) assert pytest.approx(float(phi)) == float(p.theta_phi[1]) assert str(p) == s def test_polar_init(): p = Polarization("H") assert p.theta_phi[0] == 0 assert p.theta_phi[1] == 0 assert str(p) == "H" def test_polar_circuit1(): c = Circuit(2) c.add(0, PS(phi=sp.pi/2)) assert not c.requires_polarization c = Circuit(2) c.add(0, WP(sp.pi/3, sp.pi/2)) assert c.requires_polarization def test_polar_nmode(): c = BS.H() u = c.compute_unitary() pu = c.compute_unitary(use_polarization=True) assert u.shape == (2, 2) assert pu.shape == (4, 4) assert (pu[0::2, 0::2] == u).all() assert (pu[1::2, 1::2] == u).all() def test_polar_circuit2(): c = Circuit(2) c //= BS.H() c //= (1, WP(sp.pi/4, sp.pi/2)) u = c.compute_unitary(use_symbolic=True, use_polarization=True) assert u.shape == (4, 4) assert u[0, 0] == sp.sqrt(2)/2 assert (u[2, 0] - 1/2+sp.I/2).simplify() == 0 def test_prep_state(): s, m = convert_polarized_state(BasicState("|{P:H},{P:V},0,{P:A}>")) assert str(s) == "|1,0,1,0,0,0,1,0>" assert pdisplay_matrix(m) == """ ⎡1 0 0 0 0 0 0 0 ⎤ ⎢0 1 0 0 0 0 0 0 ⎥ ⎢0 0 0 -1 0 0 0 0 ⎥ ⎢0 0 1 0 0 0 0 0 ⎥ ⎢0 0 0 0 1 0 0 0 ⎥ ⎢0 0 0 0 0 1 0 0 ⎥ ⎢0 0 0 0 0 0 sqrt(2)/2 sqrt(2)/2⎥ ⎣0 0 0 0 0 0 -sqrt(2)/2 sqrt(2)/2⎦ """.strip().replace(" ", "") s2, m2 = convert_polarized_state(BasicState("|{P:H}{P:H},{P:V},0,{P:A}>")) assert str(s2) == "|2,0,1,0,0,0,1,0>" assert (m2-m).all() == 0 def test_prep_multi_state(): convert_polarized_state(BasicState("|{P:H}{P:V},{P:V},0,{P:A}>")) def test_convert_multistate(): input_state, prep_matrix = convert_polarized_state(BasicState("|2{P:H}3{P:V}>")) assert str(input_state) == "|2,3>" def test_convert_multistate_nonorthogonal(): with pytest.raises(ValueError): convert_polarized_state(BasicState("|2{P:H}3{P:D}>")) def test_build_spatial_output(): assert sorted([str(s) for s in build_spatial_output_states(BasicState("|2,0,1>"))]) == [ '|0,2,0,0,0,1>', '|0,2,0,0,1,0>', '|1,1,0,0,0,1>', '|1,1,0,0,1,0>', '|2,0,0,0,0,1>', '|2,0,0,0,1,0>' ] def test_subcircuit_polarization(): a = Circuit(2) // PBS() // PBS() assert a.requires_polarization, "subcircuit does not propagate polarization state" b = BS.H() // a // a // BS.H() assert b.requires_polarization, "subcircuit does not propagate polarization state" ================================================ FILE: tests/utils/test_postselect.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from copy import copy from perceval.utils import BasicState, PostSelect import pytest def test_postselect_init(): ps_empty = PostSelect() # Should work ps1 = PostSelect("([0]==0 & [1,2]==1 & [3,4]==1) | [5]==0") ps2 = PostSelect("([ 0 ] ==0 and [1 , 2]== 1 & [3,4] == 1)OR[5]==0") assert ps2 == ps1 def test_postselect_init_invalid(): with pytest.raises(RuntimeError): PostSelect("[0]==0 & [1,2,]==1 & [3,4]==1 & [5]==0") # Too many commas with pytest.raises(RuntimeError): PostSelect("[0]==0 & [1,,,,2]==1 & [3,4]==1 & [5]==0") # Too many commas with pytest.raises(RuntimeError): PostSelect("[0]==0 & (1,2)==1 & [3,4]==1 & [5]==0") # Tuple syntax is not supported with pytest.raises(RuntimeError): PostSelect("[2] < 4 + [1] > 2") # Invalid separator with pytest.raises(RuntimeError): PostSelect("[2] == 4 | [1] > 2 & [0] < 1") # Invalid use of different separators with pytest.raises(RuntimeError): PostSelect("[2] >> 4") # Invalid operator with pytest.raises(RuntimeError): PostSelect("[0]==0 & [1,2]]==1 & [3,4]==1 & [5]==0") # Too many brackets => Invalid operator def test_postselect_usage(): ps_empty = PostSelect() for bs in [BasicState(), BasicState([0, 1]), BasicState([8, 8, 8, 8, 8, 8, 8, 8, 8])]: assert ps_empty(bs) ps_cnot = PostSelect("[0]==0 & [1,2]==1 & [3,4]==1 & [5]==0") for bs in [BasicState([0, 1, 0, 1, 0, 0]), BasicState([0, 0, 1, 0, 1, 0]), BasicState([0, 1, 0, 0, 1, 0]), BasicState([0, 0, 1, 1, 0, 0])]: assert ps_cnot(bs) for bs in [BasicState([1, 1, 0, 1, 0, 0]), BasicState([0, 0, 1, 0, 1, 1]), BasicState([0, 1, 1, 0, 1, 0]), BasicState([0, 0, 1, 2, 0, 0]), BasicState([0, 0, 1, 0, 0, 0])]: assert not ps_cnot(bs) assert ps_cnot(BasicState("|0,{0},0,{1},0,0>")) def test_postselect_usage_advanced_ge_le(): ps1 = PostSelect("[1,2]>=1") ps2 = PostSelect("[1,2]>0") ps3 = PostSelect("[1,2]<1") ps4 = PostSelect("[1,2]<=0") for bs in [BasicState([0, 1, 0, 1, 0, 0]), BasicState([0, 0, 1, 0, 1, 0]), BasicState([0, 1, 0, 0, 1, 0]), BasicState([0, 0, 1, 1, 0, 0])]: assert ps1(bs) assert ps2(bs) assert not ps3(bs) assert not ps4(bs) def test_postselect_operators(): ps = PostSelect("([1,2]>=1 & [3] < 2) | ([4] == 1 xor [5] > 0)") assert ps(BasicState([0, 1, 0, 1, 0, 0])) # and True assert not ps(BasicState([0, 1, 0, 2, 0, 0])) assert ps(BasicState([0, 0, 0, 0, 1, 0])) assert not ps(BasicState([0, 0, 0, 0, 1, 1])) # xor False assert ps(BasicState([0, 0, 0, 0, 1, 0])) # xor True assert ps(BasicState([0, 0, 0, 0, 0, 1])) # xor True assert not ps(BasicState([0, 0, 0, 0, 2, 0])) # xor False def test_postselect_str(): ps1 = PostSelect("[0]==0 & [1, 2 ]>0 & [3, 4]==1 & [5]<1") ps2 = PostSelect(str(ps1)) assert ps1 == ps2 def test_postselect_apply_permutation(): ps = PostSelect("[0,1]==1 & [2,3]>2 & [4,5]<3") perm_vector = [1, 2, 0] # Corresponds to PERM(perm_vector) ps.apply_permutation(perm_vector) assert ps == PostSelect("[1,2]==1 & [0,3]>2 & [4,5]<3") ps = PostSelect("[0,1]==1 & [2,3]>2 & [4,5]<3") ps.apply_permutation(perm_vector, 1) assert ps == PostSelect("[0,2]==1 & [3,1]>2 & [4,5]<3") def test_postselect_shift_modes(): initial_ps = PostSelect("[0,1]==1 & [2,3]>2 & [4,5]<3") ps = copy(initial_ps) ps.shift_modes(0) assert ps == initial_ps ps.shift_modes(2) assert ps == PostSelect("[2,3]==1 & [4,5]>2 & [6,7]<3") ps.shift_modes(-2) assert ps == initial_ps with pytest.raises(RuntimeError): ps.shift_modes(-1) def test_postselect_can_compose_with(): ps = PostSelect("[0]==0 & [1,2]==1 & [3,4]==1 & [5]==0") assert ps.can_compose_with([0]) assert ps.can_compose_with([1, 2]) assert ps.can_compose_with([3, 4]) assert ps.can_compose_with([5]) # Special cases assert ps.can_compose_with([1]) # You can compose with a subset of a condition (here [1,2]==1) assert ps.can_compose_with([6, 7]) # You can compose if the modes are not even in the post-selection conditions assert not ps.can_compose_with([2, 3]) assert not ps.can_compose_with([5, 6]) ps = PostSelect("[0]>0 & [1,2,3]<1 & [2,3,4]<1 & [5]>0") assert ps.can_compose_with([2]) assert ps.can_compose_with([2, 3]) assert not ps.can_compose_with([2, 3, 4]) assert not ps.can_compose_with([3, 4]) def test_postselect_merge(): ps1 = PostSelect("[0]==0 & [1,2]==1 & [3,4]==1 & [5]==0") ps2 = PostSelect("[5,6,7] == 1") ps1.merge(ps2) assert ps1 == PostSelect("[0]==0 & [1,2]==1 & [3,4]==1 & [5]==0 & [5,6,7] == 1") ps_empty = PostSelect() ps_empty.merge(ps1) assert ps_empty == ps1 def test_postselect_independent(): ps1 = PostSelect("[0]==0 & [1,2]==1") assert not ps1.is_independent_with(ps1) ps2 = PostSelect("[2,3] == 1") assert not ps1.is_independent_with(ps2) ps3 = PostSelect("[3]<1 & [4]<1 & [5]>0") assert ps1.is_independent_with(ps3) ================================================ FILE: tests/utils/test_qmath.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import itertools import pytest import time from perceval import BasicState, StateVector, BSDistribution, SVDistribution from perceval.utils.qmath import exponentiation_by_squaring, distinct_permutations from .._test_utils import assert_sv_close, assert_svd_close def test_exponentiation(): with pytest.raises(ValueError): exponentiation_by_squaring(12, 0) # Numbers assert exponentiation_by_squaring(12, 1), 12 assert exponentiation_by_squaring(8, 2), 64 assert exponentiation_by_squaring(52, 13), 20325604337285010030592 # Basic state bs = BasicState("|1,0,1,0,0>") assert bs**3 == bs * bs * bs # Annotated basic state annot_bs = BasicState("|0,0,{_:0}{_:1},0>") assert annot_bs**5 == annot_bs * annot_bs * annot_bs * annot_bs * annot_bs # State vector sv = StateVector(BasicState("|1,0,4,0,0,2,0,1>")) - 1j * StateVector(BasicState("|1,0,3,0,2,1,0,1>")) assert_sv_close(sv, sv) assert_sv_close(sv**2, sv * sv) assert_sv_close(sv**7, sv * sv * sv * sv * sv * sv * sv) # Basic state Distribution bsd = BSDistribution() bsd[BasicState([0, 0])] = 0.25 bsd[BasicState([1, 0])] = 0.25 bsd[BasicState([0, 1])] = 0.25 bsd[BasicState([2, 0])] = 0.125 bsd[BasicState([0, 2])] = 0.125 assert bsd**2 == bsd * bsd assert bsd**7 == bsd * bsd * bsd * bsd * bsd * bsd * bsd # State vector Distribution svd = SVDistribution() svd[StateVector([0]) + StateVector([1])] = 0.25 svd[StateVector([0]) + 1j*StateVector([1])] = 0.35 svd[StateVector([1])] = 0.4 assert_svd_close(svd, svd) assert_svd_close(svd**2, svd * svd) assert_svd_close(svd**5, svd * svd * svd * svd * svd) @pytest.mark.parametrize("parameters", [('mississippi', 0), ('mississippi', 1), ('mississippi', 6), ('mississippi', 7), ('mississippi', 12), ([0, 1, 1, 0], 0), ([0, 1, 1, 0], 1), ([0, 1, 1, 0], 2), ([0, 1, 1, 0], 3), ([0, 1, 1, 0], 4), ([0, 1, 1, 0], None), (['a'], 0), (['a'], 1), (['a'], 5), ([], 0), ([], 1), ([], 4),]) def test_distinct_permutations(parameters): iterable, r = parameters assert sorted(set(itertools.permutations(iterable, r))) == sorted(distinct_permutations(iter(iterable), r)) ================================================ FILE: tests/utils/test_seed.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import perceval as pcvl import random import numpy as np def test_seed_1(): pcvl.random_seed(25) x = random.random() pcvl.random_seed(25) y = random.random() assert x == y, "Problem with random_seed function" def test_seed2(): pcvl.random_seed(4) x1 = random.random() y1 = random.random() pcvl.random_seed(4) x2 = random.random() y2 = random.random() assert x1 == x2 and y1 == y2, "Problem with random_seed function" def test_seed3(): x = random.random() y = random.random() assert x != y, "Problem with random_seed function" ================================================ FILE: tests/utils/test_simplification.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import perceval as pcvl from perceval import Circuit from perceval.utils import Parameter from perceval.utils.algorithms.simplification import simplify from perceval.components import unitary_components as comp def PS_testing(circ, display): c2 = simplify(circ, display=display) real = [] for r, c in c2: if isinstance(c, comp.PS): phi = c.param("phi") real.append((r[0], float(phi) if phi.defined else phi.name)) return real def test_PS_simp(): phi = pcvl.P("phi") c = (Circuit(3) .add(0, comp.PS(math.pi)) .add(0, comp.PERM([2, 1, 0])) .add(0, comp.BS()) .add(2, comp.PS(phi)) .add(2, comp.PS(math.pi)) .add(0, comp.PS(math.pi / 2))) expected = [(0, 2 * math.pi), (2, "phi"), (0, math.pi / 2)] real = PS_testing(c, True) assert real == expected, "PS simplification with display = True not passed" expected = [(2, "phi"), (0, math.pi / 2)] real = PS_testing(c, False) assert real == expected, "PS simplification with display = False not passed" def PERM_testing(circ, display=False): real = [] c2 = simplify(circ, display=display) for r, c in c2: if isinstance(c, comp.PERM): real.append((r[0], c.perm_vector)) elif isinstance(c, comp.BS): real.append((r[0], c.get_variables()["theta"])) return real def test_perm_simp(): circ = (Circuit(3) .add(0, comp.PERM([0, 2, 1]))) expected = [(1, [1, 0])] real = PERM_testing(circ) assert real == expected, "PERM reduction is wrong" circ = (Circuit(3) .add(0, comp.PERM([0, 2, 1])) .add(0, comp.PERM([1, 2, 0]))) expected = [(0, [1, 0])] real = PERM_testing(circ) assert real == expected, "PERM reduction is wrong" c = (Circuit(3) .add(0, comp.PERM([2, 0, 1])) .add(0, comp.BS(theta=1)) .add(0, comp.PERM([1, 2, 0]))) expected = [(1, 1)] real = PERM_testing(c) assert real == expected, "PERM simplification moves components wrongly" c = (Circuit(4) .add(0, comp.PERM([3, 2, 1, 0])) .add(0, comp.BS(theta=1)) .add(2, comp.BS(theta=2)) .add(0, comp.PERM([3, 2, 1, 0]))) expected1 = [(0, [1, 0, 3, 2]), (2, 1), (0, 2), (0, [1, 0, 3, 2])] expected2 = [(0, [1, 0, 3, 2]), (0, 2), (2, 1), (0, [1, 0, 3, 2])] real = PERM_testing(c, True) assert real == expected1 or real == expected2, "PERM simplification moves components wrongly" def test_PS_simp_variable_param(): # There should not be a simplification for PS with variable Parameter phi = math.pi / 2 phase = Parameter("phase") phase.set_value(phi) c = Circuit(2).add(0, comp.PS(phase)).add(0, comp.PS(math.pi)) c_simp = simplify(c) assert c_simp.ncomponents() == 2 # info on Parameter not lost after simplication param = c_simp.get_parameters()[0] assert param == phase def test_PS_simp_remove_null_phase(): phi = math.pi / 2 phase = Parameter("phase") phase.set_value(phi) c = Circuit(2).add(0, comp.PS(phase)).add(0, comp.PS(0)) c_simp = simplify(c) assert c_simp.ncomponents() == 1 # info on Parameter not lost after simplification param = c_simp.get_parameters()[0] assert param == phase ================================================ FILE: tests/utils/test_stategenerator.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import math import pytest from unittest.mock import patch import perceval as pcvl import networkx as nx from .._test_utils import LogChecker def test_logical_state_raw(): sg = pcvl.StateGenerator(pcvl.Encoding.RAW) sv = sg.logical_state([0, 1, 1, 0]) assert str(sv) == "|0,1,1,0>" def test_logical_state_dual_rail(): sg = pcvl.StateGenerator(pcvl.Encoding.DUAL_RAIL) sv = sg.logical_state([0, 1, 1, 0]) assert str(sv) == "|1,0,0,1,0,1,1,0>" def test_logical_state_polarization(): sg = pcvl.StateGenerator(pcvl.Encoding.POLARIZATION) sv = sg.logical_state([0, 1, 1, 0]) assert str(sv) == "|{P:H},{P:V},{P:V},{P:H}>" def test_bell_state_raw(): sg = pcvl.StateGenerator(pcvl.Encoding.RAW) sv = sg.bell_state("phi+") bsv = pcvl.StateVector(pcvl.BasicState([0, 0])) + pcvl.StateVector(pcvl.BasicState([1, 1])) assert sv == bsv sv = sg.bell_state("phi-") bsv = pcvl.StateVector(pcvl.BasicState([0, 0])) - pcvl.StateVector(pcvl.BasicState([1, 1])) assert sv == bsv sv = sg.bell_state("psi+") bsv = pcvl.StateVector(pcvl.BasicState([0, 1])) + pcvl.StateVector(pcvl.BasicState([1, 0])) assert sv == bsv sv = sg.bell_state("psi-") bsv = pcvl.StateVector(pcvl.BasicState([0, 1])) - pcvl.StateVector(pcvl.BasicState([1, 0])) assert sv == bsv def test_bell_state_dual_rail(): sg = pcvl.StateGenerator(pcvl.Encoding.DUAL_RAIL) sv = sg.bell_state("phi+") bsv = pcvl.StateVector(pcvl.BasicState([1, 0, 1, 0])) + pcvl.StateVector(pcvl.BasicState([0, 1, 0, 1])) assert sv == bsv sv = sg.bell_state("phi-") bsv = pcvl.StateVector(pcvl.BasicState([1, 0, 1, 0])) - pcvl.StateVector(pcvl.BasicState([0, 1, 0, 1])) assert sv == bsv sv = sg.bell_state("psi+") bsv = pcvl.StateVector(pcvl.BasicState([1, 0, 0, 1])) + pcvl.StateVector(pcvl.BasicState([0, 1, 1, 0])) assert sv == bsv sv = sg.bell_state("psi-") bsv = pcvl.StateVector(pcvl.BasicState([1, 0, 0, 1])) - pcvl.StateVector(pcvl.BasicState([0, 1, 1, 0])) assert sv == bsv def test_bell_state_polarization(): sg = pcvl.StateGenerator(pcvl.Encoding.POLARIZATION) sv = sg.bell_state("phi+") bsv = pcvl.StateVector(pcvl.BasicState("|{P:H},{P:H}>")) + pcvl.StateVector(pcvl.BasicState("|{P:V},{P:V}>")) assert sv == bsv sv = sg.bell_state("phi-") bsv = pcvl.StateVector(pcvl.BasicState("|{P:H},{P:H}>")) - pcvl.StateVector(pcvl.BasicState("|{P:V},{P:V}>")) assert sv == bsv sv = sg.bell_state("psi+") bsv = pcvl.StateVector(pcvl.BasicState("|{P:H},{P:V}>")) + pcvl.StateVector(pcvl.BasicState("|{P:V},{P:H}>")) assert sv == bsv sv = sg.bell_state("psi-") bsv = pcvl.StateVector(pcvl.BasicState("|{P:H},{P:V}>")) - pcvl.StateVector(pcvl.BasicState("|{P:V},{P:H}>")) assert sv == bsv def test_ghz_state_raw(): sg = pcvl.StateGenerator(pcvl.Encoding.RAW) sv = sg.ghz_state(3) assert sv == pcvl.StateVector(pcvl.BasicState([0, 0, 0])) + pcvl.StateVector(pcvl.BasicState([1, 1, 1])) sv = sg.ghz_state(4) assert sv == pcvl.StateVector(pcvl.BasicState([0, 0, 0, 0])) + pcvl.StateVector(pcvl.BasicState([1, 1, 1, 1])) def test_ghz_state_dual_rail(): sg = pcvl.StateGenerator(pcvl.Encoding.DUAL_RAIL) sv = sg.ghz_state(3) assert sv == pcvl.StateVector(pcvl.BasicState([1, 0, 1, 0, 1, 0])) + pcvl.StateVector( pcvl.BasicState([0, 1, 0, 1, 0, 1])) sv = sg.ghz_state(4) assert sv == pcvl.StateVector(pcvl.BasicState([1, 0, 1, 0, 1, 0, 1, 0])) + pcvl.StateVector( pcvl.BasicState([0, 1, 0, 1, 0, 1, 0, 1])) def test_ghz_state_polarization(): sg = pcvl.StateGenerator(pcvl.Encoding.POLARIZATION) sv = sg.ghz_state(3) assert sv == pcvl.StateVector(pcvl.BasicState("|{P:H},{P:H},{P:H}>")) + pcvl.StateVector( pcvl.BasicState("|{P:V},{P:V},{P:V}>")) sv = sg.ghz_state(4) assert sv == pcvl.StateVector(pcvl.BasicState("|{P:H},{P:H},{P:H},{P:H}>")) + pcvl.StateVector( pcvl.BasicState("|{P:V},{P:V},{P:V},{P:V}>")) sqrt2_4 = math.sqrt(2) / 4 def test_graph_state_raw(): sg = pcvl.StateGenerator(pcvl.Encoding.RAW) sv = sg.graph_state(nx.path_graph(3)) assert pytest.approx(sv[pcvl.BasicState('|0,0,0>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|1,0,0>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|0,1,0>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|0,0,1>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|1,1,0>')]) == -sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|1,0,1>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|0,1,1>')]) == -sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|1,1,1>')]) == sqrt2_4 def test_graph_state_dual_rail(): sg = pcvl.StateGenerator(pcvl.Encoding.DUAL_RAIL) sv = sg.graph_state(nx.path_graph(3)) assert pytest.approx(sv[pcvl.BasicState('|1,0,1,0,1,0>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|0,1,1,0,1,0>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|1,0,0,1,1,0>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|1,0,1,0,0,1>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|0,1,0,1,1,0>')]) == -sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|0,1,1,0,0,1>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|1,0,0,1,0,1>')]) == -sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|0,1,0,1,0,1>')]) == sqrt2_4 def test_graph_state_polarization(): sg = pcvl.StateGenerator(pcvl.Encoding.POLARIZATION) sv = sg.graph_state(nx.path_graph(3)) assert pytest.approx(sv[pcvl.BasicState('|{P:H},{P:H},{P:H}>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|{P:V},{P:H},{P:H}>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|{P:H},{P:V},{P:H}>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|{P:H},{P:H},{P:V}>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|{P:V},{P:V},{P:H}>')]) == -sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|{P:V},{P:H},{P:V}>')]) == sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|{P:H},{P:V},{P:V}>')]) == -sqrt2_4 assert pytest.approx(sv[pcvl.BasicState('|{P:V},{P:V},{P:V}>')]) == sqrt2_4 def check_dicke_state(state: pcvl.StateVector, n: int, k: int, encoding: pcvl.Encoding): state_number = math.comb(k, n) assert state_number == len(state) photon_number = k if encoding == pcvl.Encoding.RAW: photon_number = n assert all([s_n == photon_number for s_n in state.n]) if encoding == pcvl.Encoding.DUAL_RAIL: assert 2*k == state.m else: assert state.m == k ps = pcvl.PostSelect('&'.join([f"[{i}]<2" for i in range(k)])) amp = 1/math.sqrt(state_number) for s, a in state: assert ps(s) assert a == pytest.approx(amp) @patch.object(pcvl.utils.logging.ExqaliburLogger, "warn") @pytest.mark.parametrize("encoding", [pcvl.Encoding.RAW, pcvl.Encoding.POLARIZATION, pcvl.Encoding.DUAL_RAIL]) # TODO : PostSelect should accept AnnotatedFockStates ? def test_dicke_state(mock_warn, encoding): n = 2 k = 2*n ds = pcvl.StateGenerator(encoding).dicke_state(n) check_dicke_state(ds, n, k, encoding) assert ds == pcvl.StateGenerator(encoding).dicke_state(n, k) check_dicke_state(ds, n, k, encoding) n = 4 k = 6 ds = pcvl.StateGenerator(encoding).dicke_state(n, k) check_dicke_state(ds, n, k, encoding) with pytest.raises(ValueError): pcvl.StateGenerator(encoding).dicke_state(-1) with LogChecker(mock_warn): assert pcvl.StateGenerator(encoding).dicke_state(4, 2) == pcvl.StateVector() def test_zero_padded_state(): n = 2 m = 5 state = pcvl.StateGenerator.zero_padded_state(n, m) assert state.n == n assert state.m == m assert state[:n] == pcvl.BasicState(n * [1]) with pytest.raises(AssertionError): pcvl.StateGenerator.zero_padded_state(3, 2) def test_periodic_state(): n = 2 m = 5 state = pcvl.StateGenerator.periodic_state(n, m) assert state == pcvl.BasicState([1, 0, 1, 0, 0]) with pytest.raises(AssertionError): pcvl.StateGenerator.periodic_state(2, 3) def test_evenly_spaced_state(): assert pcvl.StateGenerator.evenly_spaced_state(0, 2) == pcvl.BasicState([0, 0]) assert pcvl.StateGenerator.evenly_spaced_state(1, 5) == pcvl.BasicState([0, 0, 1, 0, 0]) assert pcvl.StateGenerator.evenly_spaced_state(2, 5) == pcvl.BasicState([1, 0, 0, 0, 1]) assert pcvl.StateGenerator.evenly_spaced_state(3, 5) == pcvl.BasicState([1, 0, 1, 0, 1]) # Test with more photons than modes assert pcvl.StateGenerator.evenly_spaced_state(8, 5) == pcvl.BasicState([2, 1, 2, 1, 2]) ================================================ FILE: tests/utils/test_statevector.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from collections import Counter import math import platform import pytest import perceval as pcvl import perceval.components.unitary_components as comp from perceval import NoiseModel, AnnotatedFockState, NoisyFockState from perceval.utils import BasicState, FockState, StateVector, SVDistribution, BSDistribution, allstate_iterator from perceval.rendering.pdisplay import pdisplay_state_distrib from .._test_utils import strip_line_12, assert_sv_close, assert_svd_close def test_basic_state(): st = BasicState([1, 0]) assert str(st) == "|1,0>" assert st.n == 1 assert isinstance(st, FockState) with pytest.raises(RuntimeError): bs = BasicState([0] * 300) # 300 modes is too much (mode count is capped at 256) def test_str_state_vector(): sv = (1 + 1j) * StateVector("|0,1>") + (1 - 1j) * StateVector("|1,0>") assert str(sv) == "(0.5+0.5I)*|0,1>+(0.5-0.5I)*|1,0>" \ or str(sv) == "(0.5-0.5I)*|1,0>+(0.5+0.5I)*|0,1>" # Order doesn't matter def test_tensor_product_0(): st1 = BasicState([0, 1]) st2 = BasicState([1]) assert str(st1 * st2) == "|0,1,1>" def test_tensor_product_1(): st1 = BasicState([1, 2]) st2 = BasicState([3, 4]) assert str(st1 * st2) == "|1,2,3,4>" def test_tensor_product_2(): st1 = BasicState("|1,{P:V}1>") assert str(st1) == "|1,{P:V}1>" st2 = BasicState("|3,{P:H}3>") assert str(st2) == "|3,{P:H}3>" assert str(st1 * st2) == "|1,{P:V}1,3,{P:H}3>" def test_tensor_svdistribution_1(): sv1 = SVDistribution() sv1.add(StateVector([0]), 0.25) sv1.add(StateVector([1]), 0.75) sv2 = SVDistribution() sv2.add(StateVector([0]), 0.8) sv2.add(StateVector([1]), 0.2) sv = sv1 * sv2 assert len(sv) == 4 assert pytest.approx(sv[StateVector([0, 0])]) == 1 / 5 assert pytest.approx(sv[StateVector([0, 1])]) == 1 / 20 assert pytest.approx(sv[StateVector([1, 0])]) == 3 / 5 assert pytest.approx(sv[StateVector([1, 1])]) == 3 / 20 def test_state_annots(): st = BasicState('|0,1,{P:V}1>') assert st.n == 3 assert st.m == 3 assert isinstance(st, AnnotatedFockState) assert str(st) == '|0,1,{P:V}1>' assert [str(a) for a in st.get_mode_annotations(1)] == [""] assert [str(a) for a in st.get_mode_annotations(2)] == ["P:V", ""] # assert st.get_photon_annotations(2) == {'P': 'V'} # assert st.get_photon_annotations(3) == {} # st.set_photon_annotations(3, {"P": "H"}) # assert str(st) == '|0,1,{P:H}{P:V}>' # st.set_photon_annotations(3, {"P": "V"}) # assert str(st) == '|0,1,2{P:V}>' assert st.get_mode_annotations(1) == [pcvl.Annotation()] assert st.get_mode_annotations(2) == [pcvl.Annotation("P:V"), pcvl.Annotation()] def test_state_identical_annots(): st1 = BasicState("|0,1,{P:V}1>") st2 = BasicState("|0,1,{P:V}1>") assert st1 == st2 st3 = BasicState("|{a:1}2{a:0},0,0>") st4 = BasicState("|2{a:0}{a:1},0,0>") assert st3 == st4 sv = 0.5 * st1 + st3 - st4 assert_sv_close(sv, StateVector(st1)) def test_state_invalid_superposition(): st1 = StateVector("|0,1>") st2 = StateVector("|1,0,0>") with pytest.raises(RuntimeError): st1 + st2 def test_state_superposition(): st1 = StateVector("|0,1>") st2 = StateVector("|1,0>") assert_sv_close(2 * st1, st1) assert_sv_close(st1 + 1j * st2, StateVector('|0,1>') + 1j * StateVector('|1,0>')) def test_init_state_vector(): st = StateVector() st += BasicState("|1,0>") assert str(st) == "|1,0>" def test_bsdistribution(): bs1 = BasicState([1, 2]) bs2 = BasicState([3, 4]) bsd = pcvl.BSDistribution() bsd[bs1] = 0.9 bsd[bs2] = 0.1 bsd_squared = bsd ** 2 assert isinstance(bsd_squared, pcvl.BSDistribution) assert len(bsd_squared) == 4 assert bsd_squared[bs1 * bs1] == pytest.approx(0.81) assert bsd_squared[bs1 * bs2] == pytest.approx(0.09) assert bsd_squared[bs2 * bs1] == pytest.approx(0.09) assert bsd_squared[bs2 * bs2] == pytest.approx(0.01) bsd_mult = pcvl.BSDistribution(bs1) * pcvl.BSDistribution({bs1: 0.4, bs2: 0.6}) assert len(bsd_mult) == 2 assert bsd_mult[bs1 * bs1] == pytest.approx(0.4) assert bsd_mult[bs1 * bs2] == pytest.approx(0.6) assert bsd.m == 2 assert bsd_squared.m == 4 with pytest.raises(ValueError): pcvl.BSDistribution({BasicState("|1>"): .5, BasicState("|1,1>"): .5}) def test_svdistribution(): st1 = StateVector("|0,1>") st2 = StateVector("|1,0>") svd = SVDistribution() svd.add(st1, 0.5) svd[st2] = 0.5 assert strip_line_12(pdisplay_state_distrib(svd)) == strip_line_12(""" +-------+-------------+ | state | probability | +-------+-------------+ | |0,1> | 1/2 | | |1,0> | 1/2 | +-------+-------------+""") or strip_line_12(pdisplay_state_distrib(svd)) == strip_line_12(""" +-------+-------------+ | state | probability | +-------+-------------+ | |1,0> | 1/2 | | |0,1> | 1/2 | +-------+-------------+""") svd_squared = svd ** 2 assert isinstance(svd_squared, SVDistribution) assert len(svd_squared) == 4 assert svd_squared[StateVector("|1,0,1,0>")] == pytest.approx(1 / 4) assert svd_squared[StateVector("|1,0,0,1>")] == pytest.approx(1 / 4) assert svd_squared[StateVector("|0,1,1,0>")] == pytest.approx(1 / 4) assert svd_squared[StateVector("|0,1,0,1>")] == pytest.approx(1 / 4) assert svd.m == 2 assert svd.n_max == 1 assert svd_squared.m == 4 assert svd_squared.n_max == 2 with pytest.raises(ValueError): svd[StateVector("|1>")] = 1/7 SVDistribution({StateVector("|1>"): .5, StateVector("|1,1>"): .5}) def test_distribution_simplification(): bs1 = BasicState("|0,1,0,0>") bs2 = BasicState("|0,3,0,0>") bs3 = BasicState("|0,0,2,1>") bs4 = BasicState("|0,0,1,1>") bs5 = BasicState("|0,0,0,1>") bsd = BSDistribution() bsd.add(bs1, 0.3) bsd.add(bs2, 0.1) bsd.add(bs3, 0.1) bsd.add(bs4, 0.2) bsd.add(bs5, 0.3) photon_threshold = 1 simp_p1 = bsd.photon_threshold_simplification(photon_threshold) assert simp_p1[bs1] == pytest.approx(0.4) assert simp_p1[bs4] == pytest.approx(0.3) assert simp_p1[bs5] == pytest.approx(0.3) bs1 = BasicState("|1,1,0,0>") bs2 = BasicState("|0,2,0,0>") bs3 = BasicState("|0,0,1,1>") bs4 = BasicState("|0,1,0,1>") bs5 = BasicState("|1,0,0,1>") bsd = BSDistribution() bsd.add(bs1, 0.3) bsd.add(bs2, 0.1) bsd.add(bs3, 0.1) bsd.add(bs4, 0.2) bsd.add(bs5, 0.3) group_size = 2 simp_g2 = bsd.group_modes_simplification(group_size) assert simp_g2[BasicState("|2,0>")] == pytest.approx(0.4) assert simp_g2[BasicState("|0,2>")] == pytest.approx(0.1) assert simp_g2[BasicState("|1,1>")] == pytest.approx(0.5) group_size = 3 simp_g3 = bsd.group_modes_simplification(group_size) assert simp_g3[BasicState("|2,0>")] == pytest.approx(0.4) assert simp_g3[BasicState("|1,1>")] == pytest.approx(0.6) def test_separate_state_with_annots(): st1 = BasicState("|0,{1}>") assert st1.separate_state()[0] == BasicState("|0,1>") # st2 = BasicState("|{1},{P:V}>") # does not mean anything now # assert st2.separate_state() == [BasicState("|1,1>")] st3 = BasicState("|{1},{2}>") assert [bs for bs in st3.separate_state()] == [BasicState("|1,0>"), BasicState("|0,1>")] st4 = BasicState("|{1},{0}>") assert [bs for bs in st4.separate_state()] == [BasicState("|0,1>"), BasicState("|1,0>")] st5 = BasicState("|{0},{0},{3}>") assert [bs for bs in st5.separate_state()] == [BasicState("|1,1,0>"), BasicState("|0,0,1>")] def test_partition(): st1 = BasicState("|1,1,1>") partition = st1.partition([2, 1]) expected = ["|1,1,0> |0,0,1>", "|1,0,1> |0,1,0>", "|0,1,1> |1,0,0>"] result = [] for a_subset in partition: result.append(" ".join([str(state) for state in a_subset])) for r, e in zip(sorted(result), sorted(expected)): assert r == e def test_parse_annot(): invalid_str = ["|{0}", "|1{2>", "{P:(0.3,0)>", "|{;}>", "|{P:(1,2,3)}>", "|{P:(1,a)}>", "|{a:0,a:1}>"] for s in invalid_str: with pytest.raises(ValueError): BasicState(s) st1 = BasicState("|{0}{1}>") st1 = st1.clear_annotations() assert st1 == BasicState("|2>") st1 = BasicState("|{0}{1},0,1>") st1 = st1.clear_annotations() assert st1 == BasicState("|2,0,1>") def test_sv_parse_symb_annot(): st1 = BasicState("|{P:1.5707963268}>") assert str(st1) == "|{P:D}>" def test_sv_parse_tuple_annot(): st1 = BasicState("|{P:(0.30,0)}>") assert str(st1) == "|{P:0.3}>" # st1 = BasicState("|{P:(pi/2,0.3)}>") # assert str(st1) == "|{P:(pi/2,0.3)}>" def test_svd_sample(): noise = NoiseModel(g2=0.1, indistinguishability=0.9) qpu = pcvl.Processor("Naive", comp.BS(), noise) qpu.with_input(BasicState([1, 0])) sample = qpu.source_distribution.sample(1) assert isinstance(sample, list) assert isinstance(sample[0], StateVector) assert sample[0] in qpu.source_distribution sample = qpu.source_distribution.sample(2) assert len(sample) == 2 assert isinstance(sample[0], StateVector) assert isinstance(sample[1], StateVector) def test_svd_anonymize_annots_simple(): svd = SVDistribution({ StateVector(BasicState("|{1},{2},{3}>")): 0.2, StateVector(BasicState("|{4},{5},{6}>")): 0.3, StateVector(BasicState("|{1},{3},{3}>")): 0.4, StateVector(BasicState("|{0},{8},{8}>")): 0.1, }) svd2 = pcvl.anonymize_annotations(svd) assert_svd_close(svd2, SVDistribution( { StateVector(NoisyFockState('|{0},{1},{2}>')): 0.5, StateVector(NoisyFockState('|{0},{1},{1}>')): 0.5 })) @pytest.mark.skipif( platform.system() != "Windows", reason="Superposed states aren't anonymized the same given C++ unordered containers OS-specific implementation") def test_svd_anonymize_annots_superposition(): svd = SVDistribution({ StateVector(AnnotatedFockState("|{_:0},{_:0},{_:1}>")) + StateVector(AnnotatedFockState("|{_:0},{_:2},{_:1}>")): 0.1, StateVector(AnnotatedFockState("|{_:3},{_:4},{_:4}>")): 0.1, StateVector(AnnotatedFockState("|{_:0},{_:0},{_:1}>")) + StateVector(AnnotatedFockState("|{_:0},{_:4},{_:1}>")): 0.1, StateVector(AnnotatedFockState("|{_:4},{_:4},{_:2}>")): 0.1, StateVector(AnnotatedFockState("|{_:1},{_:3},{_:3}>")): 0.1, StateVector(AnnotatedFockState("|{_:0},{_:2},{_:3}>")): 0.5, }) svd2 = pcvl.anonymize_annotations(svd) assert_svd_close(svd2, SVDistribution( { StateVector(AnnotatedFockState('|{a:0},{a:1},{a:2}>')): 0.5, StateVector(AnnotatedFockState('|{a:0},{a:0},{a:1}>')) + StateVector(AnnotatedFockState('|{a:0},{a:2},{a:1}>')): 0.2, StateVector(AnnotatedFockState('|{a:0},{a:1},{a:1}>')): 0.2, StateVector(AnnotatedFockState('|{a:0},{a:0},{a:1}>')): 0.1 })) def test_statevector_sample(): sv = StateVector("|0,1>") + StateVector("|1,0>") counter = Counter() for _ in range(20): counter[sv.sample()] += 1 states = [str(s) for s in counter] assert len(states) == 2 and "|1,0>" in states and "|0,1>" in states def test_statevector_samples(): sv = StateVector("|0,1>") + StateVector("|1,0>") counter = Counter() for s in sv.samples(20): counter[s] += 1 states = [str(s) for s in counter] assert len(states) == 2 and "|1,0>" in states and "|0,1>" in states def test_statevector_measure_1(): sv = StateVector("|0,1>") + StateVector("|1,0>") map_measure_sv = sv.measure([0]) assert len(map_measure_sv) == 2 and \ BasicState("|0>") in map_measure_sv and \ BasicState("|1>") in map_measure_sv assert pytest.approx(0.5) == map_measure_sv[BasicState("|0>")][0] assert map_measure_sv[BasicState("|0>")][1] == StateVector("|1>") assert pytest.approx(0.5) == map_measure_sv[BasicState("|1>")][0] assert map_measure_sv[BasicState("|1>")][1] == StateVector("|0>") def test_statevector_measure_2(): sv = StateVector("|0,1>") + StateVector("|1,0>") map_measure_sv = sv.measure([0, 1]) assert len(map_measure_sv) == 2 and \ BasicState("|0,1>") in map_measure_sv and \ BasicState("|1,0>") in map_measure_sv assert pytest.approx(0.5) == map_measure_sv[BasicState("|0,1>")][0] assert map_measure_sv[BasicState("|0,1>")][1] == StateVector("|>") assert pytest.approx(0.5) == map_measure_sv[BasicState("|1,0>")][0] assert map_measure_sv[BasicState("|1,0>")][1] == StateVector("|>") def test_statevector_measure_3(): sv = StateVector("|0,1,1>") + StateVector("|1,1,0>") map_measure_sv = sv.measure([1]) assert len(map_measure_sv) == 1 and \ BasicState("|1>") in map_measure_sv assert pytest.approx(1) == map_measure_sv[BasicState("|1>")][0] assert_sv_close(map_measure_sv[BasicState("|1>")][1], StateVector([0, 1]) + StateVector([1, 0])) def test_statevector_equality(): gamma = math.pi / 2 st1 = StateVector(AnnotatedFockState("|{P:H},{P:H}>")) st2 = StateVector(AnnotatedFockState("|{P:H},{P:V}>")) input_state = math.cos(gamma) * st1 + math.sin(gamma) * st2 assert input_state == st2 def test_statevector_arithmetic(): sv1 = StateVector() sv1 += StateVector([0, 1]) sv1 += StateVector([1, 0]) assert_sv_close(sv1, StateVector([0, 1]) + StateVector([1, 0])) sv2 = StateVector() sv2 += 0.5 * StateVector([0, 1]) sv2 += -0.5 * StateVector([1, 0]) assert_sv_close(sv2, StateVector([0, 1]) - StateVector([1, 0])) sv3 = sv1 + sv2 assert sv3 == StateVector([0, 1]) sv4 = 0.2j * sv1 - 0.6j * sv2 assert_sv_close(sv4, -math.sqrt(5) / 5 * 1j * StateVector([0, 1]) + math.sqrt(5) / 5 * 2j * StateVector([1, 0])) sv5 = StateVector() sv5 += 0.2j * sv1 sv5 += -0.6j * sv2 assert_sv_close(sv5, -math.sqrt(5) / 5 * 1j * StateVector([0, 1]) + math.sqrt(5) / 5 * 2j * StateVector([1, 0])) def test_max_photon_state_iterator(): l = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2] ] i = 0 for bs in pcvl.utils.max_photon_state_iterator(3,2): assert bs == pcvl.BasicState(l[i]) i += 1 def test_allstate_iterator(): in_bs = BasicState([1,0,0,0]) iteration_result = list(allstate_iterator(in_bs)) assert len(iteration_result) == 4 assert BasicState([1, 0, 0, 0]) in iteration_result assert BasicState([0, 1, 0, 0]) in iteration_result assert BasicState([0, 0, 1, 0]) in iteration_result assert BasicState([0, 0, 0, 1]) in iteration_result in_sv = StateVector([0]+[0]*5) + StateVector([1]+[0]*5) + StateVector([2]+[0]*5) iteration_result = list(allstate_iterator(in_sv)) assert len(iteration_result) == 28 # (C2,7 + C1,6 + C0,6 = 21 + 6 + 1) def test_mult(): fs1 = pcvl.BasicState("|2,0>") fs2 = pcvl.BasicState("|3,5>") state_basic = pcvl.BasicState([4, 1]) svd = pcvl.SVDistribution({fs1: .6, fs2: .4}) assert svd * state_basic == pcvl.SVDistribution({fs1 * state_basic: .6, fs2 * state_basic: .4}) assert state_basic * svd == pcvl.SVDistribution({state_basic * fs1: .6, state_basic * fs2: .4}) bsd = pcvl.BSDistribution({fs1: .6, fs2: .4}) assert bsd * state_basic == pcvl.BSDistribution({fs1 * state_basic: .6, fs2 * state_basic: .4}) assert state_basic * bsd == pcvl.BSDistribution({state_basic * fs1: .6, state_basic * fs2: .4}) ================================================ FILE: tests/utils/test_tensorproduct.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval import BasicState, StateVector, BSDistribution, SVDistribution from .._test_utils import assert_svd_close, assert_bsd_close sv0 = StateVector([0, 1]) + StateVector([1, 1]) * 1j sv1 = StateVector([2, 3]) + StateVector([4, 5]) bs = BasicState([6, 7]) def _assert_sv_approx_eq(sv1: StateVector, sv2: StateVector, error_msg="Assertion error"): sv1.normalize() sv2.normalize() for state in sv1.keys(): assert state in sv2, error_msg assert sv1[state] == pytest.approx(sv2[state]), error_msg def test_mul(): result = sv0 * sv1 expected = (0.5 * StateVector([0, 1, 2, 3]) + 0.5j * StateVector([1, 1, 2, 3]) + 0.5 * StateVector([0, 1, 4, 5]) + 0.5j * StateVector([1, 1, 4, 5])) _assert_sv_approx_eq(result, expected, "SV multiplication is wrong") result = sv0 * bs expected = (0.5 * 2 ** 0.5 * StateVector([0, 1, 6, 7]) + 0.5 * 2 ** 0.5 * 1j * StateVector([1, 1, 6, 7])) _assert_sv_approx_eq(result, expected, "SV with BS multiplication is wrong") result = StateVector(bs) * sv0 expected = (0.5 * 2 ** 0.5 * StateVector([6, 7, 0, 1]) + 0.5 * 2 ** 0.5 * 1j * StateVector([6, 7, 1, 1])) _assert_sv_approx_eq(result, expected, "BS with SV multiplication is wrong") def test_power(): power = 5 result = sv0 ** power expected = sv0 for _ in range(power - 1): expected *= sv0 _assert_sv_approx_eq(result, expected, "SV pow is wrong") result = bs ** power expected = BasicState([6, 7] * power) assert result == expected, "BS pow is wrong" def test_bsd_tensor_product(): bsd_1 = BSDistribution({BasicState([2, 3]): .4, BasicState([0, 1]): .6}) bsd_2 = BSDistribution({BasicState([4, 5]): .3, BasicState([6, 7]): .7}) bsd_3 = BSDistribution({BasicState([8, 9]): .3, BasicState([10, 11]): .5, BasicState([12, 13]): .2}) assert_bsd_close(BSDistribution.tensor_product(bsd_1, bsd_2), bsd_1 * bsd_2) assert_bsd_close(BSDistribution.tensor_product(bsd_1, bsd_2, merge_modes=True), BSDistribution({ BasicState([6, 8]): .4 * .3 + .6 * .7, BasicState([8, 10]): .4 * .7, BasicState([4, 6]): .6 * .3, })) assert_bsd_close(BSDistribution.tensor_product(bsd_1, bsd_2, merge_modes=True), BSDistribution.tensor_product(bsd_2, bsd_1, merge_modes=True)) assert_bsd_close(BSDistribution.list_tensor_product([bsd_1, bsd_2, bsd_3]), bsd_1 * bsd_2 * bsd_3) product = BSDistribution(BasicState(bsd_1.m)) for bsd in [bsd_1, bsd_2, bsd_3]: product = BSDistribution.tensor_product(product, bsd, merge_modes=True) assert_bsd_close(BSDistribution.list_tensor_product([bsd_1, bsd_2, bsd_3], merge_modes=True), product) # Now with empty BSD bsd_list = [bsd_1, bsd_2, BSDistribution(), bsd_3] assert BSDistribution.list_tensor_product(bsd_list, merge_modes=True) == BSDistribution(), \ "Wrong list tensor product result when merge_modes is True and there are empty BSD" def test_svd_tensor_product(): svd_1 = SVDistribution({StateVector([2, 3]): .4, StateVector([0, 1]): .6}) svd_2 = SVDistribution({StateVector([4, 5]): .3, StateVector([6, 7]): .7}) svd_3 = SVDistribution({StateVector([8, 9]): .3, StateVector([10, 11]): .5, StateVector([12, 13]): .2}) assert_svd_close(SVDistribution.tensor_product(svd_1, svd_2), svd_1 * svd_2) assert_svd_close(SVDistribution.list_tensor_product([svd_1, svd_2, svd_3]), svd_1 * svd_2 * svd_3) # Now with empty SVD bsd_list = [svd_1, svd_2, SVDistribution(), svd_3] assert_svd_close(SVDistribution.list_tensor_product(bsd_list), SVDistribution()) ================================================ FILE: tests/utils/test_utils_conversion.py ================================================ # MIT License # # Copyright (c) 2022 Quandela # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # As a special exception, the copyright holders of exqalibur library give you # permission to combine exqalibur with code included in the standard release of # Perceval under the MIT license (or modified versions of such code). You may # copy and distribute such a combined system following the terms of the MIT # license for both exqalibur and Perceval. This exception for the usage of # exqalibur is limited to the python bindings used by Perceval. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest from perceval.utils.conversion import BSCount, BSDistribution, BSSamples, \ probs_to_sample_count, probs_to_samples, \ sample_count_to_probs, sample_count_to_samples, samples_to_probs, samples_to_sample_count from perceval.utils import BasicState b0 = BasicState([0, 0, 0, 1]) b1 = BasicState([0, 0, 1, 0]) b2 = BasicState([0, 1, 0, 0]) b3 = BasicState([1, 0, 0, 0]) def test_samples_to_sample_count(): sample_list = BSSamples() sample_list.extend([b0, b1, b2, b3]) output = samples_to_sample_count(sample_list) assert len(output) == 4 for s in sample_list: assert output[s] == 1 sample_list = BSSamples() sample_list.extend([b0, b0, b1, b3, b0, b1, b3, b1, b2, b0, b0, b3, b0]) output = samples_to_sample_count(sample_list) assert len(output) == 4 assert output[b0] == 6 assert output[b1] == 3 assert output[b2] == 1 assert output[b3] == 3 assert len(samples_to_sample_count(BSSamples())) == 0 def test_sample_count_to_probs(): sample_count = BSCount({ b0: 280, b1: 120, b2: 400, b3: 200 }) output = sample_count_to_probs(sample_count) assert sum(output.values()) == 1 assert output[b0] == pytest.approx(0.28) assert output[b1] == pytest.approx(0.12) assert output[b2] == pytest.approx(0.4) assert output[b3] == pytest.approx(0.2) empty = sample_count_to_probs(BSCount()) assert len(empty) == 0 @pytest.mark.parametrize("count", [1000, 1e9, 170, 1, 0]) def test_probs_to_sample_count(count): bsd = BSDistribution() bsd[b0] = 0.01 bsd[b1] = 0.25 bsd[b2] = 0.15 bsd[b3] = 0.59 output = probs_to_sample_count(bsd, max_samples=count) assert sum(output.values()) == count if count > 100: # Need enough samples to be accurate assert output[b0] < output[b2] assert output[b2] < output[b1] assert output[b1] < output[b3] assert sum(list(output.values())) == count def test_sample_count_to_samples(): sample_count = BSCount({ b0: 280, b1: 120, b2: 400, b3: 200 }) samples = sample_count_to_samples(sample_count) for state, count in sample_count.items(): assert count * 0.7 < samples.count(state) < count * 1.3 def test_probs_to_samples(): bsd = BSDistribution() bsd[b0] = 0.1 bsd[b1] = 0.25 bsd[b2] = 0.15 bsd[b3] = 0.5 output = probs_to_samples(bsd, max_samples=1000) assert len(output) == 1000 def test_samples_to_probs(): sample_list = BSSamples() sample_list.extend([b0, b0, b1, b0, b1, b1, b2, b0, b0, b0]) output = samples_to_probs(sample_list) assert len(output) == 3 assert output[b0] == pytest.approx(.6) assert output[b1] == pytest.approx(.3) assert output[b2] == pytest.approx(.1)